Compare commits

..

2 Commits

Author SHA1 Message Date
JohannVelazquez 72b7602e8e Bitácora 8–30 jun: contrato firmado, plan de actividades y skill proposal-pdf
- Comunicaciones (REGISTRO #15–#21): respuesta v1.1 (10-jun), luz verde (16-jun),
  contrato (envío 25-jun, ajustes + firma 26-jun), arranque con Erika y kickoff 1-jul
- Evidencia en fuentes/ (correos 8/10/16-jun, WhatsApp Paola y Erika)
- Contratos (sin firmar y firmado) en propuesta/
- Plan de actividades Etapa 0–3 + guion del kickoff (planeacion/)
- Skill proposal-pdf instalado (.claude/skills) + Propuesta-Balam.pdf regenerada
- README/PENDIENTES al día; limpieza de archivos sueltos; .gitignore (locks de Office)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:42:45 -06:00
JohannVelazquez 633d05e330 Reorganiza repo como fuente de la verdad + propuesta v1.0 con emisión de facturas
- Estructura nueva: README maestro, bitacora/ (REGISTRO, PENDIENTES, plantillas),
  propuesta/, fuentes/; material superado a _archivado/
- Propuesta v1.0: MVP BIND-first con emisión asistida MXN/USD (dry-run +
  confirmación, timbra PAC de BIND), 112-136 h / $67,200-$81,600 + IVA,
  stack .NET 10 + EF Core + Angular 21 + PostgreSQL 17 sobre Azure
- Bitácora: historial de correos + 2 llamadas (incl. revisión 4-jun) y pendientes
- Prototipo y diagrama actualizados a v1.0; precios de Azure verificados
- Archivo ajeno (proyecto EOS) retirado del repo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:57:27 -06:00
86 changed files with 35704 additions and 472 deletions
+228
View File
@@ -0,0 +1,228 @@
---
name: proposal-pdf
description: >-
Build polished, print-optimized PDF proposals, quotes, and reports with an
editorial design system (navy + terracotta, serif display headings, full-bleed
cover, auto-numbered table of contents, stamped confidential footers). Use this
skill WHENEVER the user wants a professional/branded PDF deliverable from
content — e.g. "make this proposal a PDF", "generate a commercial proposal /
cotización / propuesta", "turn this into a nice client-facing PDF", "build a
quote/report PDF", or any request for a high-quality multi-page PDF document
that needs a cover, table of contents, tables, or page numbers. Prefer this
over ad-hoc HTML-to-PDF or reportlab-from-scratch whenever presentation quality
matters. Works in Spanish or English.
---
# proposal-pdf
Produces a refined, print-ready PDF from hand-authored HTML using headless
Chromium, with an automatically numbered table of contents and stamped footers.
This is the same pipeline used to build real commercial proposals — it favors
typographic quality over speed.
## Why this approach
Tools like `reportlab`-from-scratch or `pandoc` give you a document but not a
*designed* one. This skill instead has you author one print-optimized HTML file
against a ready-made design system, then renders it with Chromium (which has the
best CSS print engine available). Two things that are otherwise painful are
handled for you:
- **Full-bleed cover** — a dark cover that runs edge-to-edge, via `@page:first { margin:0 }`.
- **Real TOC page numbers** — resolved in a second render pass by searching the
rendered PDF, so they're always correct regardless of how content paginates.
## Setup (once)
```bash
pip install playwright pdfplumber pypdf reportlab
python -m playwright install chromium
```
### Fonts — bundled and embedded (no system install needed)
The design uses **Caladea** (serif display), **Carlito** (sans body) and
**DejaVu Sans Mono** (code). These ship with the skill in `assets/fonts/`, and
`template.html` embeds them with `@font-face` rules that point at a `fonts/`
folder **next to the HTML**. The output therefore does not depend on which fonts
happen to be installed on the build machine. Rules of the road:
- When you copy `template.html` to your working file, **also copy `assets/fonts/`
next to it** so the `url("fonts/…")` paths resolve. `build_pdf.py` renders each
document from its own folder, so relative paths (fonts, logos, images) just work.
- If the fonts are missing at build time, Chromium silently falls back to system
fonts (Cambria/Calibri/…) — it looks *close* but isn't identical. After building,
`build_pdf.py` inspects the finished PDF and **prints a warning** if Caladea or
Carlito didn't embed, so that regression can't ship unnoticed.
- Caladea/Carlito are *metric-compatible* with Cambria/Calibri: swapping them
changes the glyph shapes but **not** the pagination. So if a rebuild has a
different page count than an older PDF, the **content** changed — not the fonts.
The footer text is stamped separately with a TTF set in the config
(`fonts.footer_ttf`); if that path doesn't exist it falls back to Helvetica.
### En este proyecto (Windows / Balam)
El skill ya está instalado en `.claude/skills/proposal-pdf/`. Para usarlo aquí:
```powershell
# 1) Dependencias (una vez)
pip install playwright pdfplumber pypdf reportlab
python -m playwright install chromium
# 2) Construir (desde la raíz del repo)
python .claude/skills/proposal-pdf/scripts/build_pdf.py <ruta>/pdf.config.json --check # valida
python .claude/skills/proposal-pdf/scripts/build_pdf.py <ruta>/pdf.config.json # construye
```
- **Fuentes:** las fuentes del diseño (Caladea/Carlito/DejaVu Sans Mono) van **embebidas** vía `@font-face` desde `propuesta/fonts/`, así que el PDF sale idéntico sin depender de lo que tenga instalado Windows. No instales nada. Para el **texto del footer** (que se estampa aparte con reportlab) sí se usa una TTF del sistema: `fonts.footer_ttf` apunta a `C:\Windows\Fonts\calibri.ttf`. Si al reconstruir ves el aviso `⚠ design font(s) missing`, es que la carpeta `fonts/` no quedó junto al HTML.
- **Verificación visual:** en este entorno **no hay `pdftoppm`/poppler**, así que el paso "verify visually" se hace con el helper incluido, que usa `pypdfium2` (ya viene con `pdfplumber`):
```powershell
python .claude/skills/proposal-pdf/scripts/render_check.py <ruta>/Salida.pdf 1 2 3 # rasteriza páginas a PNG
```
Luego abre los PNG (o pídeme que los lea). Siempre revisa: portada full-bleed, números de TOC presentes y plausibles, tablas que no se desborden, y que el footer aparezca en el cuerpo pero **no** en la portada.
- **Consola UTF-8:** el script ya fuerza salida UTF-8 (la consola de Windows es cp1252 y truena con los glifos ✓/⚠). No necesitas hacer nada.
## Files in this skill
```
proposal-pdf/
├── SKILL.md
├── scripts/
│ └── build_pdf.py # the render → number → stamp engine
├── assets/
│ ├── template.html # the design system + every component, with examples
│ ├── fonts/ # bundled design fonts (Caladea, Carlito, DejaVu Sans Mono)
│ ├── pdf.config.example.json # the full config schema (documented)
│ └── test.config.json # smoke test for the template
└── examples/ # a second, complete worked example
├── example-a4.html # A4 + cover logo + a long page-spanning table
└── example-a4.config.json # shows A4, "X / N" footer, skip_pages
```
Smoke-test the install end-to-end:
```bash
python3 scripts/build_pdf.py assets/test.config.json # Letter template
python3 scripts/build_pdf.py examples/example-a4.config.json # A4 worked example
```
## Workflow
### 1. Author the content as HTML
Copy `assets/template.html` to a working file (e.g. `proposal.html`) and replace
the example content with the real content. **Keep the CSS and the component
markup/classes** — that's the design system. The big in-file comment and the
component cheat-sheet at the top of the template tell you which class does what.
Common building blocks: numbered section headers (`.sec-head` + `.sec-num`),
tables with `td.k` / `td.num` / `tr.total` / `tr.sub-total` / `td.cov`, accent
panels (`.callout`, `.callout.cool`), a pipeline diagram (`.flow` + `.chip`),
headline stat cards (`.synthesis` + `.stat`), and phase blocks (`.etapa`).
For each section that appears in the table of contents:
- put a `{{PG_<key>}}` token in its TOC row (already wired in the template), and
- remember a **unique phrase from that section's body** (see step 2).
Appendices/new-page sections get the `.break` class to start on a fresh page.
### 2. Write the config
Copy `assets/pdf.config.example.json` to `pdf.config.json` (next to your HTML)
and fill it in. The important part is the `toc` map: each key matches a
`{{PG_<key>}}` token, and its value is a phrase the engine will search for to
find that section's page.
> **Anchor rules — read this, it prevents the two failure modes:**
> 1. **Use BODY text, never the section title.** Titles also render in the TOC,
> so a title would match the TOC page first and give the wrong number.
> 2. **Don't start the anchor on the first letter of a drop-cap paragraph.** The
> CSS drop cap splits the first letter into its own glyph, so the extracted
> text reads `"T his…"`. Start the anchor a word or two in
> (e.g. `"opening paragraph…"`, not `"This opening paragraph…"`).
>
> Good anchor: the first ~610 words of the first normal paragraph after the
> heading. Keep it distinctive.
### 3. Build
```bash
python scripts/build_pdf.py pdf.config.json --check # validate config + env first (optional)
python scripts/build_pdf.py pdf.config.json # build
```
`--check` runs a **preflight**: confirms the deps and Chromium are installed, the
input HTML exists, and — importantly — that every `{{PG_*}}` token in the HTML
has a matching anchor in `toc` (and warns about anchors with no token). Fix any
reported issue before building.
The build renders once with tokens blanked, prints the resolved page numbers,
fills the TOC, re-renders, stamps footers, sets metadata, and writes the PDF. If
an anchor can't be found it stops and tells you exactly which one. If an anchor
matches more than one page it warns and uses the first — make it more specific if
that's wrong.
### 4. ALWAYS verify visually
Rendering bugs are visual, so look at the result. Rasterize a few pages and
inspect them (don't just trust that it ran):
```bash
pdftoppm -png -r 80 -f 1 -l 1 Output.pdf check_cover # full-bleed cover
pdftoppm -png -r 80 -f 2 -l 2 Output.pdf check_toc # TOC page numbers filled
# …and a content page or two
```
Check: the cover fills the page edge-to-edge, the TOC numbers are present and
plausible, tables don't overflow, and the footer shows on body pages but not the
cover. Re-author and rebuild as needed — iteration is normal.
## Customizing
- **Colors / fonts:** edit the `:root` CSS variables in your HTML (`--ink`,
`--accent`, `--cream`, the font stacks). One accent color used sparingly reads
as more premium than many.
- **Page size:** set `"page_size"` to `"Letter"`, `"Legal"`, `"A4"`, `"A3"`, or a
custom `{"width_mm":210,"height_mm":297}`. The engine derives the footer
geometry automatically — you do **not** edit the script. For A4/A3 also change
`@page { size:Letter }` to the matching size in the HTML CSS (there's a comment
there). See `examples/example-a4.html` for a full A4 document.
- **Cover logo:** drop a `<div class="logo">` into the cover `.top` — either a
text mark (`<span class="txt">NAME</span>`) or an image
(`<img class="invert" src="logo.png">`; `.invert` whitens a dark logo on the
navy cover). Example in `examples/example-a4.html`.
- **Footer:** all in `footer` — `text`, `rule` (hairline on/off), `font_size_pt`,
`margin_mm` (distance from the bottom), `color` ([r,g,b] 01),
`page_number_format` (`"{page}"`, `"{page} / {pages}"`, `"Página {page}"`),
`skip_first_page`, and `skip_pages` (extra 1-based pages to skip, e.g. the TOC).
- **No cover / no TOC:** a doc without `{{PG_*}}` tokens just skips the numbering
pass (leave `toc` empty `{}`). Set `footer.skip_first_page` to `false` if
there's no cover.
- **Long tables & page breaks:** handled for you — table headers repeat on every
page a table spans, rows/callouts/cards never split, and headings won't be
stranded at a page bottom. Wrap anything else you want kept together in
`class="keep"`; force a new page with `class="break"`.
## Design principles baked in (keep these)
- One serif for display (cover, section numbers, headings), one sans for body,
one mono for code. Don't add more families.
- A single accent color (terracotta) for rules, section numbers, and the one
callout per section that matters. Everything else is navy/ink and warm neutrals.
- Generous hairlines and uppercase micro-labels instead of heavy boxes.
- Numeric table columns are right-aligned with tabular figures (`.num`); the
total row is the only emphasized row.
- Keep prose tight. The format rewards restraint.
## Troubleshooting
- **"Could not locate these TOC anchors"** → the phrase isn't body text on that
page, or it starts on a drop-cap letter, or whitespace differs. Pick a longer
distinctive body phrase. (Whitespace is normalized automatically.)
- **Cover not full-bleed** → confirm `@page:first { margin:0 }` is present and
the build runs with Chromium margins at 0 (it does by default). The cover
`.cover` element is sized to the full page (216mm × 279mm for Letter).
- **Emoji not in color** (✅⚠️❌ in coverage tables) → install a color emoji font
(`fonts-noto-color-emoji` on Debian/Ubuntu). Chromium uses it automatically.
- **Footer font looks wrong** → set a valid `fonts.footer_ttf`; otherwise it
uses Helvetica.
- **Playwright errors about browsers** → run `python -m playwright install chromium`.
@@ -0,0 +1,43 @@
{
"_comment": "Copy to pdf.config.json next to your HTML and edit. Relative paths resolve against THIS file. Run a dry validation with: python3 scripts/build_pdf.py pdf.config.json --check",
"input_html": "proposal.html",
"output_pdf": "Proposal.pdf",
"_comment_page": "page_size: 'Letter' | 'Legal' | 'A4' | 'A3', or a custom object {\"width_mm\":210,\"height_mm\":297}. For A4 also set @page size:A4 in the HTML CSS.",
"page_size": "Letter",
"cover_full_bleed": true,
"render_timeout_ms": 30000,
"metadata": {
"title": "Commercial Proposal — Project Title",
"author": "Your Name",
"subject": "One-line description of the proposal",
"keywords": "proposal, optional"
},
"footer": {
"_comment": "Stamped on body pages. skip_first_page hides it on the cover; skip_pages is extra 1-based pages to skip (e.g. the TOC). page_number_format supports {page} and {pages}, e.g. '{page}' or '{page} / {pages}' or 'Pagina {page}'.",
"text": "Commercial Proposal · Project Title · Client — Confidential",
"skip_first_page": true,
"skip_pages": [],
"page_number_format": "{page}",
"rule": true,
"font_size_pt": 7.5,
"margin_mm": 12,
"color": [0.46, 0.51, 0.57]
},
"fonts": {
"_comment": "TTF for the footer text. Falls back to Helvetica if the path is missing. Debian/Ubuntu: fonts-crosextra-carlito installs this path.",
"footer_ttf": "/usr/share/fonts/truetype/crosextra/Carlito-Regular.ttf"
},
"toc": {
"_comment": "key -> a UNIQUE phrase from that section's BODY (NOT its title; titles also render in the TOC and match there first). Avoid the first letter of a drop-cap paragraph. Match the keys to the {{PG_*}} tokens in the HTML. Leave this empty {} for a document with no table of contents.",
"execsum": "opening paragraph uses a drop cap",
"1": "A lede paragraph introduces the section",
"2": "Body text for the scope section",
"anexoA": "Appendices start on a fresh page"
}
}
@@ -0,0 +1,366 @@
<!DOCTYPE html>
<!--
template.html — editorial proposal/report design system.
HOW TO USE
• Replace the example content with yours. Keep the component markup/classes.
• Every section that appears in the TOC gets a {{PG_key}} token in the TOC row
AND a matching entry in pdf.config.json -> "toc" whose value is a UNIQUE
phrase from that section's BODY (never the title — titles also render in the
TOC and would match there first). build_pdf.py fills the tokens automatically.
• The first page is a full-bleed cover thanks to @page:first { margin:0 }.
• Footer (confidential line + page number) is stamped by build_pdf.py, not here.
COMPONENT CHEAT-SHEET (classes you can reuse)
.cover .eyebrow/.rule/h1/.client/.meta-grid → cover page
.toc + .toc-row(.section) → table of contents
.sec + .sec-head/.sec-num/.kick → numbered section header
.sec-head.no-num → header with no big number
h3 > span.sn → sub-section heading (e.g. 2.1)
table / th / td.k / td.num / td.cov / .ctr → tables (key cell, numeric, coverage)
tr.total / tr.sub-total → emphasized table rows
table.compact → tighter table for dense data
.callout / .callout.cool → accent / navy info panels
.flow + .chip(.accent) → pipeline / step diagram
.synthesis + .stat → headline stat cards
p.lede → opening paragraph
p.drop → paragraph with drop cap
p.mini-label → small uppercase label
.etapa + .etapa-h → phase/stage block
.signoff → closing signature
.cover .logo (.txt | img.invert) → optional logo on the cover
.break → start the block on a new page
.keep / .no-break → keep a block from splitting across pages
-->
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="author" content="Your Name">
<title>Proposal Title</title>
<style>
/* ===========================================================================
DESIGN FONTS — embedded, so output does NOT depend on system-installed fonts.
The .ttf files ship in the skill at assets/fonts/. When you copy this template
to your working file, copy that fonts/ folder next to your HTML too (so these
url("fonts/…") paths resolve). Without it the PDF silently falls back to
Cambria/Calibri and won't match the intended look — build_pdf.py warns when
that happens. You don't need to edit this block.
=========================================================================== */
@font-face{ font-family:"Caladea"; font-style:normal; font-weight:400; src:url("fonts/Caladea-Regular.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:normal; font-weight:700; src:url("fonts/Caladea-Bold.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:italic; font-weight:400; src:url("fonts/Caladea-Italic.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:italic; font-weight:700; src:url("fonts/Caladea-BoldItalic.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:normal; font-weight:400; src:url("fonts/Carlito-Regular.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:normal; font-weight:700; src:url("fonts/Carlito-Bold.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:italic; font-weight:400; src:url("fonts/Carlito-Italic.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:italic; font-weight:700; src:url("fonts/Carlito-BoldItalic.ttf") format("truetype"); }
@font-face{ font-family:"DejaVu Sans Mono"; font-style:normal; font-weight:400; src:url("fonts/DejaVuSansMono.ttf") format("truetype"); }
:root{
--ink:#1C2B39; /* deep navy — primary text + cover */
--ink-soft:#33424F; /* secondary text */
--muted:#6B7682; /* captions, footers */
--accent:#BC5B3E; /* terracotta — the single accent */
--accent-d:#9E4A30; /* darker terracotta for emphasis on tint */
--accent-tint:#F6E9E3; /* faint terracotta fill */
--cream:#F4EFEA; /* warm panel fill */
--line:#D9DEE3; /* hairlines */
--serif:"Caladea", Georgia, "Times New Roman", serif;
--sans:"Carlito", "Helvetica Neue", Arial, sans-serif;
--mono:"DejaVu Sans Mono", "SFMono-Regular", Consolas, monospace;
}
/* For A4: change size to A4 here AND set "page_size":"A4" in pdf.config.json. */
@page{ size:Letter; margin:15mm 16mm 18mm 16mm; }
@page :first{ margin:0; } /* full-bleed cover */
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
body{
font-family:var(--sans); color:var(--ink-soft);
font-size:10pt; line-height:1.5;
-webkit-print-color-adjust:exact; print-color-adjust:exact;
}
p{ margin:0 0 7pt; }
strong,b{ color:var(--ink); font-weight:700; }
em{ font-style:italic; }
code{
font-family:var(--mono); font-size:8.6pt;
background:var(--cream); padding:.5pt 3pt; border-radius:2pt; color:var(--ink);
}
a{ color:var(--accent-d); text-decoration:none; }
/* ---------- COVER ---------- */
.cover{
width:216mm; min-height:279mm; background:var(--ink); color:#EAEDF0;
padding:30mm 26mm 24mm; display:flex; flex-direction:column;
position:relative; overflow:hidden;
}
.cover::after{ /* faint geometric corner motif */
content:""; position:absolute; right:-60mm; top:-60mm;
width:150mm; height:150mm; border-radius:50%;
background:radial-gradient(circle at center, rgba(188,91,62,.18), rgba(188,91,62,0) 70%);
}
.cover .top,.cover .mid,.cover .meta{ position:relative; z-index:2; }
.cover .rule{ width:46pt; height:3pt; background:var(--accent); margin-bottom:14pt; }
.cover .eyebrow{
font-size:9pt; letter-spacing:.32em; text-transform:uppercase; color:#9FB0BF; font-weight:700;
}
/* Optional logo slot (top of cover). Use a text mark or an <img>.
.invert flips a dark logo to white for the navy cover. */
.cover .logo{ margin-bottom:16pt; }
.cover .logo .txt{ font-family:var(--serif); font-weight:700; font-size:14pt; color:#fff; letter-spacing:.01em; }
.cover .logo img{ height:30pt; width:auto; }
.cover .logo img.invert{ filter:brightness(0) invert(1); opacity:.92; }
.cover .mid{ margin-top:auto; margin-bottom:auto; padding:18mm 0; }
.cover h1{
font-family:var(--serif); font-weight:700; font-size:37pt; line-height:1.06;
letter-spacing:-.01em; margin:0; color:#FFFFFF; max-width:150mm;
}
.cover .client{ margin-top:16pt; font-family:var(--sans); font-size:12.5pt; color:#C9D1D8; }
.cover .client b{ color:var(--accent); font-weight:700; }
.cover .confidential{
margin-top:18pt; display:flex; justify-content:space-between;
font-size:8pt; letter-spacing:.18em; text-transform:uppercase; color:#7E8C99;
}
.meta-grid{ display:grid; grid-template-columns:34mm 1fr; gap:6pt 10pt; margin:0; font-size:9.2pt; }
.meta-grid dt{ color:#8A98A5; text-transform:uppercase; letter-spacing:.12em; font-size:7.6pt; padding-top:1.5pt; }
.meta-grid dd{ margin:0; color:#D7DDE2; }
.meta-grid dd .who{ display:block; }
.meta-grid dd .who b{ color:#FFFFFF; font-weight:700; }
.meta-grid dd .who span{ color:#9FB0BF; }
/* ---------- TOC ---------- */
.toc{ break-before:page; padding-top:6mm; }
.kick{ font-size:8.5pt; letter-spacing:.28em; text-transform:uppercase; color:var(--accent); font-weight:700; }
.toc h2{ font-family:var(--serif); font-size:26pt; font-weight:700; color:var(--ink); margin:2pt 0 4pt; }
.toc .bar{ width:40pt; height:3pt; background:var(--accent); margin:6pt 0 16pt; }
.toc-row{ display:flex; align-items:baseline; gap:8pt; padding:7pt 0; border-bottom:.6pt solid var(--line); font-size:10.5pt; }
.toc-row .n{ width:22pt; color:var(--accent); font-weight:700; font-variant-numeric:tabular-nums; }
.toc-row .t{ color:var(--ink); }
.toc-row.section .t{ font-weight:700; }
.toc-row .dots{ flex:1; border-bottom:1pt dotted var(--line); transform:translateY(-3pt); }
.toc-row .pg{ color:var(--muted); font-variant-numeric:tabular-nums; }
/* ---------- SECTIONS ---------- */
.content{ break-before:page; }
.sec{ margin-bottom:14pt; }
.sec-head{ display:flex; gap:12pt; align-items:flex-start; border-bottom:1.4pt solid var(--ink); padding-bottom:6pt; margin:0 0 11pt; }
.sec-head .sec-num{ font-family:var(--serif); font-size:30pt; font-weight:700; line-height:.9; color:var(--accent); min-width:42pt; }
.sec-head h2{ font-family:var(--serif); font-size:19pt; font-weight:700; color:var(--ink); margin:4pt 0 0; }
.sec-head .kick{ display:block; margin-bottom:2pt; }
.sec-head.no-num{ border-bottom-width:1.4pt; }
.sec-head.no-num h2{ margin-top:0; }
h3{ font-family:var(--sans); font-size:11.5pt; font-weight:700; color:var(--ink); margin:13pt 0 5pt; }
h3 .sn{ color:var(--accent); font-weight:700; margin-right:7pt; }
h4{ font-family:var(--sans); font-size:9.5pt; font-weight:700; color:var(--ink-soft); text-transform:uppercase; letter-spacing:.06em; margin:10pt 0 3pt; }
p.lede{ font-size:11pt; color:var(--ink-soft); }
p.drop::first-letter{
font-family:var(--serif); font-size:34pt; font-weight:700; color:var(--accent);
float:left; line-height:.82; padding:2pt 6pt 0 0;
}
p.mini-label{ font-size:8pt; letter-spacing:.12em; text-transform:uppercase; color:var(--accent-d); font-weight:700; margin:9pt 0 1pt; }
ul,ol{ margin:4pt 0 8pt; padding-left:16pt; }
li{ margin:0 0 3.5pt; padding-left:2pt; }
li::marker{ color:var(--accent); }
/* ---------- TABLES ---------- */
table{ width:100%; border-collapse:collapse; margin:10pt 0; font-size:9.2pt; }
thead th{
text-align:left; font-size:7.8pt; letter-spacing:.07em; text-transform:uppercase;
color:#FFFFFF; background:var(--ink); padding:5pt 8pt; font-weight:700;
}
thead th.num,thead th.ctr{ text-align:right; }
thead th.ctr{ text-align:center; }
tbody td{ padding:5.5pt 8pt; border-bottom:.6pt solid var(--line); vertical-align:top; }
tbody tr:nth-child(even) td{ background:#FBFAF8; }
td.k{ color:var(--ink); font-weight:600; }
.num,th.num{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
tr.total td{ font-weight:700; color:var(--ink); background:var(--cream); border-top:1.2pt solid var(--ink); border-bottom:1.2pt solid var(--ink); }
tr.sub-total td{ font-weight:700; color:var(--ink); background:var(--accent-tint); border-top:1pt solid var(--accent); }
td.cov{ font-size:11pt; text-align:center; line-height:1; }
table.compact tbody td{ padding:4pt 8pt; }
table.compact{ font-size:8.8pt; }
/* ---------- CALLOUTS ---------- */
.callout{
background:var(--accent-tint); border-left:3pt solid var(--accent);
padding:9pt 12pt; margin:10pt 0; font-size:9.4pt; color:var(--ink-soft); border-radius:0 3pt 3pt 0;
}
.callout strong,.callout b{ color:var(--accent-d); }
.callout.cool{ background:var(--cream); border-left-color:var(--ink); }
.callout.cool strong,.callout.cool b{ color:var(--ink); }
/* ---------- FLOW / PIPELINE ---------- */
.flow{ display:flex; flex-wrap:wrap; align-items:center; gap:5pt; margin:10pt 0; }
.flow .chip{
background:#fff; border:1pt solid var(--line); border-radius:4pt;
padding:4pt 9pt; font-size:8.6pt; font-weight:600; color:var(--ink-soft); white-space:nowrap;
}
.flow .chip.accent{ background:var(--accent); border-color:var(--accent); color:#fff; }
.flow .arrow{ color:var(--accent); font-weight:700; }
/* ---------- SYNTHESIS STATS ---------- */
.synthesis{ display:flex; gap:10pt; margin:12pt 0; }
.synthesis .stat{ flex:1; background:var(--ink); color:#EAEDF0; border-radius:5pt; padding:11pt 13pt; }
.synthesis .stat .big{ font-family:var(--serif); font-size:21pt; font-weight:700; color:#fff; line-height:1; }
.synthesis .stat .lab{ font-size:8pt; letter-spacing:.08em; text-transform:uppercase; color:#9FB0BF; margin-top:4pt; }
/* ---------- PHASE / STAGE ---------- */
.etapa{ border:1pt solid var(--line); border-left:3pt solid var(--accent); border-radius:0 4pt 4pt 0; padding:9pt 12pt; margin:8pt 0; }
.etapa-h{ font-weight:700; color:var(--ink); font-size:10pt; margin-bottom:3pt; }
.etapa-h .tag{ float:right; font-size:8pt; color:var(--muted); font-weight:600; }
/* ---------- SIGNOFF ---------- */
.signoff{ margin-top:18pt; padding-top:10pt; border-top:1.4pt solid var(--ink); }
.signoff .nm{ font-family:var(--serif); font-size:14pt; font-weight:700; color:var(--ink); }
.signoff .rl{ font-size:8.5pt; color:var(--muted); letter-spacing:.04em; }
/* ---------- PRINT SAFETY (keep these) ---------- */
/* Don't strand a heading at the bottom of a page, just above its body. */
h2,h3,h4,.sec-head{ break-after:avoid; }
/* Repeat table header rows when a long table spans a page break, and never
split an individual row, callout, card, phase block or the sign-off. */
thead{ display:table-header-group; }
tfoot{ display:table-footer-group; }
tr{ break-inside:avoid; }
.callout,.synthesis,.synthesis .stat,.etapa,.flow,.signoff,figure,img{ break-inside:avoid; }
/* Utilities: .keep keeps a block together; .break starts a new page. */
.keep,.no-break{ break-inside:avoid; }
.break{ break-before:page; }
</style>
</head>
<body>
<!-- ============================= COVER ============================= -->
<section class="cover">
<div class="top">
<!-- Optional logo: <div class="logo"><span class="txt">YOUR MARK</span></div>
or <div class="logo"><img class="invert" src="logo.png" alt="Logo"></div>
(use file:// abs path or a path next to the HTML; .invert whitens a dark logo) -->
<div class="rule"></div>
<div class="eyebrow">Commercial Proposal</div>
</div>
<div class="mid">
<h1>Project Title<br>Goes Here</h1>
<div class="client"><b>Client Name</b></div>
</div>
<div class="meta">
<dl class="meta-grid">
<dt>Prepared by</dt>
<dd>Your Name — Role · City</dd>
<dt>Date</dt>
<dd>1 January 2026</dd>
<dt>Version</dt>
<dd>1.0</dd>
<dt>Valid for</dt>
<dd>30 calendar days from issue</dd>
</dl>
<div class="confidential">
<span>Confidential document</span>
<span>Valid 30 days</span>
</div>
</div>
</section>
<!-- ============================= TOC ============================= -->
<!-- One .toc-row per section. The {{PG_key}} token is resolved by build_pdf.py. -->
<section class="toc">
<div class="kick">Contents</div>
<h2>Index</h2>
<div class="bar"></div>
<div class="toc-row"><span class="n"></span><span class="t">Executive summary</span><span class="dots"></span><span class="pg">{{PG_execsum}}</span></div>
<div class="toc-row section"><span class="n">01</span><span class="t">Understanding</span><span class="dots"></span><span class="pg">{{PG_1}}</span></div>
<div class="toc-row section"><span class="n">02</span><span class="t">Scope</span><span class="dots"></span><span class="pg">{{PG_2}}</span></div>
<div class="toc-row section"><span class="n">A</span><span class="t">Appendix A — Traceability</span><span class="dots"></span><span class="pg">{{PG_anexoA}}</span></div>
</section>
<!-- ============================= BODY ============================= -->
<main class="content">
<!-- ===== Executive summary: header with NO big number + drop cap ===== -->
<section class="sec">
<div class="sec-head no-num"><div><h2>Executive summary</h2></div></div>
<p class="drop">This opening paragraph uses a drop cap. Keep the executive summary tight: the problem, the proposed first step, and the outcome. The first sentence here doubles as a good TOC anchor — pick a distinctive phrase and put it in the config.</p>
<div class="synthesis">
<div class="stat"><div class="big">67</div><div class="lab">weeks</div></div>
<div class="stat"><div class="big">$XX K</div><div class="lab">investment + tax</div></div>
<div class="stat"><div class="big">4</div><div class="lab">milestones</div></div>
</div>
</section>
<!-- ===== Section 1: numbered header + flow diagram + callout ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">01</div><div><h2>Understanding</h2></div></div>
<p class="lede">A lede paragraph introduces the section in a slightly larger size. Distinctive opening text makes a reliable TOC anchor.</p>
<h3><span class="sn">1.1</span>The pipeline</h3>
<div class="flow">
<span class="chip">Source</span><span class="arrow"></span>
<span class="chip">Transform</span><span class="arrow"></span>
<span class="chip accent">Platform</span><span class="arrow"></span>
<span class="chip">Output</span>
</div>
<div class="callout"><strong>Key risk to validate.</strong> Use accent callouts for the one or two things the reader must not miss. Use the <code>.cool</code> variant for neutral notes.</div>
</section>
<!-- ===== Section 2: tables (stack + total row) + phase blocks ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">02</div><div><h2>Scope</h2></div></div>
<p>Body text for the scope section, with a distinctive opening phrase for the anchor.</p>
<table>
<thead><tr><th>Layer</th><th>Technology</th><th class="num">Version</th></tr></thead>
<tbody>
<tr><td class="k">Backend</td><td>.NET</td><td class="num">10</td></tr>
<tr><td class="k">Frontend</td><td>Angular</td><td class="num">21</td></tr>
<tr><td class="k">Database</td><td>PostgreSQL</td><td class="num">16</td></tr>
</tbody>
</table>
<div class="etapa"><div class="etapa-h">Stage 0 — Discovery<span class="tag">~1 week</span></div>Short description of the stage and its deliverable.</div>
<div class="etapa"><div class="etapa-h">Stage 1 — Core<span class="tag">~2 weeks</span></div>Short description of the stage and its deliverable.</div>
<table>
<thead><tr><th>Stage</th><th class="num">Hours</th><th class="num">Investment (MXN)</th></tr></thead>
<tbody>
<tr><td class="k">Discovery</td><td class="num">18 22</td><td class="num">$10,800 $13,200</td></tr>
<tr><td class="k">Core</td><td class="num">32 39</td><td class="num">$19,200 $23,400</td></tr>
<tr class="total"><td>Total</td><td class="num">50 61 h</td><td class="num">$30,000 $36,600</td></tr>
</tbody>
</table>
</section>
<!-- ===== Appendix A: annex header + coverage table (emoji column) ===== -->
<section class="sec break">
<div class="sec-head"><div class="sec-num">A</div><div><span class="kick">Appendix</span><h2>Traceability</h2></div></div>
<p class="lede">Appendices start on a fresh page via the <code>.break</code> class. Distinctive opening phrase for the anchor here too.</p>
<div class="callout cool"><strong>Legend:</strong>&nbsp;&nbsp;✅ Covered&nbsp;&nbsp;·&nbsp;&nbsp;⚠️ Partial&nbsp;&nbsp;·&nbsp;&nbsp;❌ Deferred</div>
<table class="compact">
<thead><tr><th style="width:46pt;">ID</th><th>Requirement</th><th class="ctr" style="width:62pt;">Coverage</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="k">RF-01</td><td>Example requirement</td><td class="cov"></td><td>Included</td></tr>
<tr><td class="k">RF-02</td><td>Another requirement</td><td class="cov">⚠️</td><td>Partial in MVP</td></tr>
<tr><td class="k">RF-03</td><td>A deferred requirement</td><td class="cov"></td><td>Later phase</td></tr>
</tbody>
</table>
<div class="signoff">
<div class="nm">Your Name</div>
<div class="rl">Role · City</div>
</div>
</section>
</main>
</body>
</html>
@@ -0,0 +1,15 @@
{
"_comment": "Smoke test: builds the template itself so you can verify the skill works end-to-end. Run: python3 scripts/build_pdf.py assets/test.config.json",
"input_html": "template.html",
"output_pdf": "test_out.pdf",
"metadata": { "title": "Test Proposal", "author": "Test Author", "subject": "Smoke test" },
"footer": { "text": "Commercial Proposal · Test · Client — Confidential", "skip_first_page": true },
"fonts": { "footer_ttf": "/usr/share/fonts/truetype/crosextra/Carlito-Regular.ttf" },
"toc": {
"_comment": "anchors must be unique BODY phrases, never section titles",
"execsum": "opening paragraph uses a drop cap",
"1": "A lede paragraph introduces the section",
"2": "Body text for the scope section",
"anexoA": "Appendices start on a fresh page"
}
}
@@ -0,0 +1,31 @@
{
"_comment": "Worked example: A4 + cover logo + a long table that spans a page break + 'X / N' footer. Run: python3 scripts/build_pdf.py examples/example-a4.config.json",
"input_html": "example-a4.html",
"output_pdf": "Propuesta-Reservaciones-A4.pdf",
"page_size": "A4",
"cover_full_bleed": true,
"metadata": {
"title": "Propuesta — Plataforma de Reservaciones",
"author": "Johann Velázquez",
"subject": "Plataforma de reservaciones para Náutica del Norte",
"keywords": "propuesta, reservaciones, software"
},
"footer": {
"_comment": "skip_pages also skips the TOC (page 2) here; page_number_format shows X / N.",
"text": "Propuesta · Plataforma de Reservaciones · Náutica del Norte — Confidencial",
"skip_first_page": true,
"skip_pages": [2],
"page_number_format": "{page} / {pages}",
"rule": true
},
"fonts": {
"footer_ttf": "/usr/share/fonts/truetype/crosextra/Carlito-Regular.ttf"
},
"toc": {
"execsum": "El objetivo de la primera etapa es centralizar",
"1": "El reto principal del proyecto",
"2": "El alcance se organiza en módulos",
"3": "El proyecto se entrega bajo un esquema",
"anexoA": "Este anexo detalla la cobertura"
}
}
@@ -0,0 +1,362 @@
<!DOCTYPE html>
<!--
template.html — editorial proposal/report design system.
HOW TO USE
• Replace the example content with yours. Keep the component markup/classes.
• Every section that appears in the TOC gets a {{PG_key}} token in the TOC row
AND a matching entry in pdf.config.json -> "toc" whose value is a UNIQUE
phrase from that section's BODY (never the title — titles also render in the
TOC and would match there first). build_pdf.py fills the tokens automatically.
• The first page is a full-bleed cover thanks to @page:first { margin:0 }.
• Footer (confidential line + page number) is stamped by build_pdf.py, not here.
COMPONENT CHEAT-SHEET (classes you can reuse)
.cover .eyebrow/.rule/h1/.client/.meta-grid → cover page
.toc + .toc-row(.section) → table of contents
.sec + .sec-head/.sec-num/.kick → numbered section header
.sec-head.no-num → header with no big number
h3 > span.sn → sub-section heading (e.g. 2.1)
table / th / td.k / td.num / td.cov / .ctr → tables (key cell, numeric, coverage)
tr.total / tr.sub-total → emphasized table rows
table.compact → tighter table for dense data
.callout / .callout.cool → accent / navy info panels
.flow + .chip(.accent) → pipeline / step diagram
.synthesis + .stat → headline stat cards
p.lede → opening paragraph
p.drop → paragraph with drop cap
p.mini-label → small uppercase label
.etapa + .etapa-h → phase/stage block
.signoff → closing signature
.cover .logo (.txt | img.invert) → optional logo on the cover
.break → start the block on a new page
.keep / .no-break → keep a block from splitting across pages
-->
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="author" content="Johann Velázquez">
<title>Propuesta — Plataforma de Reservaciones</title>
<style>
:root{
--ink:#1C2B39; /* deep navy — primary text + cover */
--ink-soft:#33424F; /* secondary text */
--muted:#6B7682; /* captions, footers */
--accent:#BC5B3E; /* terracotta — the single accent */
--accent-d:#9E4A30; /* darker terracotta for emphasis on tint */
--accent-tint:#F6E9E3; /* faint terracotta fill */
--cream:#F4EFEA; /* warm panel fill */
--line:#D9DEE3; /* hairlines */
--serif:"Caladea", Georgia, "Times New Roman", serif;
--sans:"Carlito", "Helvetica Neue", Arial, sans-serif;
--mono:"DejaVu Sans Mono", "SFMono-Regular", Consolas, monospace;
}
/* For A4: change size to A4 here AND set "page_size":"A4" in pdf.config.json. */
@page{ size:A4; margin:15mm 16mm 18mm 16mm; }
@page :first{ margin:0; } /* full-bleed cover */
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
body{
font-family:var(--sans); color:var(--ink-soft);
font-size:10pt; line-height:1.5;
-webkit-print-color-adjust:exact; print-color-adjust:exact;
}
p{ margin:0 0 7pt; }
strong,b{ color:var(--ink); font-weight:700; }
em{ font-style:italic; }
code{
font-family:var(--mono); font-size:8.6pt;
background:var(--cream); padding:.5pt 3pt; border-radius:2pt; color:var(--ink);
}
a{ color:var(--accent-d); text-decoration:none; }
/* ---------- COVER ---------- */
.cover{
width:216mm; min-height:279mm; background:var(--ink); color:#EAEDF0;
padding:30mm 26mm 24mm; display:flex; flex-direction:column;
position:relative; overflow:hidden;
}
.cover::after{ /* faint geometric corner motif */
content:""; position:absolute; right:-60mm; top:-60mm;
width:150mm; height:150mm; border-radius:50%;
background:radial-gradient(circle at center, rgba(188,91,62,.18), rgba(188,91,62,0) 70%);
}
.cover .top,.cover .mid,.cover .meta{ position:relative; z-index:2; }
.cover .rule{ width:46pt; height:3pt; background:var(--accent); margin-bottom:14pt; }
.cover .eyebrow{
font-size:9pt; letter-spacing:.32em; text-transform:uppercase; color:#9FB0BF; font-weight:700;
}
/* Optional logo slot (top of cover). Use a text mark or an <img>.
.invert flips a dark logo to white for the navy cover. */
.cover .logo{ margin-bottom:16pt; }
.cover .logo .txt{ font-family:var(--serif); font-weight:700; font-size:14pt; color:#fff; letter-spacing:.01em; }
.cover .logo img{ height:30pt; width:auto; }
.cover .logo img.invert{ filter:brightness(0) invert(1); opacity:.92; }
.cover .mid{ margin-top:auto; margin-bottom:auto; padding:18mm 0; }
.cover h1{
font-family:var(--serif); font-weight:700; font-size:37pt; line-height:1.06;
letter-spacing:-.01em; margin:0; color:#FFFFFF; max-width:150mm;
}
.cover .client{ margin-top:16pt; font-family:var(--sans); font-size:12.5pt; color:#C9D1D8; }
.cover .client b{ color:var(--accent); font-weight:700; }
.cover .confidential{
margin-top:18pt; display:flex; justify-content:space-between;
font-size:8pt; letter-spacing:.18em; text-transform:uppercase; color:#7E8C99;
}
.meta-grid{ display:grid; grid-template-columns:34mm 1fr; gap:6pt 10pt; margin:0; font-size:9.2pt; }
.meta-grid dt{ color:#8A98A5; text-transform:uppercase; letter-spacing:.12em; font-size:7.6pt; padding-top:1.5pt; }
.meta-grid dd{ margin:0; color:#D7DDE2; }
.meta-grid dd .who{ display:block; }
.meta-grid dd .who b{ color:#FFFFFF; font-weight:700; }
.meta-grid dd .who span{ color:#9FB0BF; }
/* ---------- TOC ---------- */
.toc{ break-before:page; padding-top:6mm; }
.kick{ font-size:8.5pt; letter-spacing:.28em; text-transform:uppercase; color:var(--accent); font-weight:700; }
.toc h2{ font-family:var(--serif); font-size:26pt; font-weight:700; color:var(--ink); margin:2pt 0 4pt; }
.toc .bar{ width:40pt; height:3pt; background:var(--accent); margin:6pt 0 16pt; }
.toc-row{ display:flex; align-items:baseline; gap:8pt; padding:7pt 0; border-bottom:.6pt solid var(--line); font-size:10.5pt; }
.toc-row .n{ width:22pt; color:var(--accent); font-weight:700; font-variant-numeric:tabular-nums; }
.toc-row .t{ color:var(--ink); }
.toc-row.section .t{ font-weight:700; }
.toc-row .dots{ flex:1; border-bottom:1pt dotted var(--line); transform:translateY(-3pt); }
.toc-row .pg{ color:var(--muted); font-variant-numeric:tabular-nums; }
/* ---------- SECTIONS ---------- */
.content{ break-before:page; }
.sec{ margin-bottom:14pt; }
.sec-head{ display:flex; gap:12pt; align-items:flex-start; border-bottom:1.4pt solid var(--ink); padding-bottom:6pt; margin:0 0 11pt; }
.sec-head .sec-num{ font-family:var(--serif); font-size:30pt; font-weight:700; line-height:.9; color:var(--accent); min-width:42pt; }
.sec-head h2{ font-family:var(--serif); font-size:19pt; font-weight:700; color:var(--ink); margin:4pt 0 0; }
.sec-head .kick{ display:block; margin-bottom:2pt; }
.sec-head.no-num{ border-bottom-width:1.4pt; }
.sec-head.no-num h2{ margin-top:0; }
h3{ font-family:var(--sans); font-size:11.5pt; font-weight:700; color:var(--ink); margin:13pt 0 5pt; }
h3 .sn{ color:var(--accent); font-weight:700; margin-right:7pt; }
h4{ font-family:var(--sans); font-size:9.5pt; font-weight:700; color:var(--ink-soft); text-transform:uppercase; letter-spacing:.06em; margin:10pt 0 3pt; }
p.lede{ font-size:11pt; color:var(--ink-soft); }
p.drop::first-letter{
font-family:var(--serif); font-size:34pt; font-weight:700; color:var(--accent);
float:left; line-height:.82; padding:2pt 6pt 0 0;
}
p.mini-label{ font-size:8pt; letter-spacing:.12em; text-transform:uppercase; color:var(--accent-d); font-weight:700; margin:9pt 0 1pt; }
ul,ol{ margin:4pt 0 8pt; padding-left:16pt; }
li{ margin:0 0 3.5pt; padding-left:2pt; }
li::marker{ color:var(--accent); }
/* ---------- TABLES ---------- */
table{ width:100%; border-collapse:collapse; margin:10pt 0; font-size:9.2pt; }
thead th{
text-align:left; font-size:7.8pt; letter-spacing:.07em; text-transform:uppercase;
color:#FFFFFF; background:var(--ink); padding:5pt 8pt; font-weight:700;
}
thead th.num,thead th.ctr{ text-align:right; }
thead th.ctr{ text-align:center; }
tbody td{ padding:5.5pt 8pt; border-bottom:.6pt solid var(--line); vertical-align:top; }
tbody tr:nth-child(even) td{ background:#FBFAF8; }
td.k{ color:var(--ink); font-weight:600; }
.num,th.num{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
tr.total td{ font-weight:700; color:var(--ink); background:var(--cream); border-top:1.2pt solid var(--ink); border-bottom:1.2pt solid var(--ink); }
tr.sub-total td{ font-weight:700; color:var(--ink); background:var(--accent-tint); border-top:1pt solid var(--accent); }
td.cov{ font-size:11pt; text-align:center; line-height:1; }
table.compact tbody td{ padding:4pt 8pt; }
table.compact{ font-size:8.8pt; }
/* ---------- CALLOUTS ---------- */
.callout{
background:var(--accent-tint); border-left:3pt solid var(--accent);
padding:9pt 12pt; margin:10pt 0; font-size:9.4pt; color:var(--ink-soft); border-radius:0 3pt 3pt 0;
}
.callout strong,.callout b{ color:var(--accent-d); }
.callout.cool{ background:var(--cream); border-left-color:var(--ink); }
.callout.cool strong,.callout.cool b{ color:var(--ink); }
/* ---------- FLOW / PIPELINE ---------- */
.flow{ display:flex; flex-wrap:wrap; align-items:center; gap:5pt; margin:10pt 0; }
.flow .chip{
background:#fff; border:1pt solid var(--line); border-radius:4pt;
padding:4pt 9pt; font-size:8.6pt; font-weight:600; color:var(--ink-soft); white-space:nowrap;
}
.flow .chip.accent{ background:var(--accent); border-color:var(--accent); color:#fff; }
.flow .arrow{ color:var(--accent); font-weight:700; }
/* ---------- SYNTHESIS STATS ---------- */
.synthesis{ display:flex; gap:10pt; margin:12pt 0; }
.synthesis .stat{ flex:1; background:var(--ink); color:#EAEDF0; border-radius:5pt; padding:11pt 13pt; }
.synthesis .stat .big{ font-family:var(--serif); font-size:21pt; font-weight:700; color:#fff; line-height:1; }
.synthesis .stat .lab{ font-size:8pt; letter-spacing:.08em; text-transform:uppercase; color:#9FB0BF; margin-top:4pt; }
/* ---------- PHASE / STAGE ---------- */
.etapa{ border:1pt solid var(--line); border-left:3pt solid var(--accent); border-radius:0 4pt 4pt 0; padding:9pt 12pt; margin:8pt 0; }
.etapa-h{ font-weight:700; color:var(--ink); font-size:10pt; margin-bottom:3pt; }
.etapa-h .tag{ float:right; font-size:8pt; color:var(--muted); font-weight:600; }
/* ---------- SIGNOFF ---------- */
.signoff{ margin-top:18pt; padding-top:10pt; border-top:1.4pt solid var(--ink); }
.signoff .nm{ font-family:var(--serif); font-size:14pt; font-weight:700; color:var(--ink); }
.signoff .rl{ font-size:8.5pt; color:var(--muted); letter-spacing:.04em; }
/* ---------- PRINT SAFETY (keep these) ---------- */
/* Don't strand a heading at the bottom of a page, just above its body. */
h2,h3,h4,.sec-head{ break-after:avoid; }
/* Repeat table header rows when a long table spans a page break, and never
split an individual row, callout, card, phase block or the sign-off. */
thead{ display:table-header-group; }
tfoot{ display:table-footer-group; }
tr{ break-inside:avoid; }
.callout,.synthesis,.synthesis .stat,.etapa,.flow,.signoff,figure,img{ break-inside:avoid; }
/* Utilities: .keep keeps a block together; .break starts a new page. */
.keep,.no-break{ break-inside:avoid; }
.break{ break-before:page; }
</style>
</head>
<body>
<!-- ============================= COVER ============================= -->
<section class="cover">
<div class="top">
<div class="logo"><span class="txt">BRÚJULA</span></div>
<div class="rule"></div>
<div class="eyebrow">Propuesta Comercial</div>
</div>
<div class="mid">
<h1>Plataforma de<br>Reservaciones</h1>
<div class="client">Preparada para <b>Náutica del Norte</b></div>
</div>
<div class="meta">
<dl class="meta-grid">
<dt>Preparada por</dt>
<dd>Johann Velázquez — Consultor de Software · Monterrey, N.L.</dd>
<dt>Fecha</dt>
<dd>16 de junio de 2026</dd>
<dt>Versión</dt>
<dd>1.0</dd>
<dt>Vigencia</dt>
<dd>30 días naturales a partir de la fecha de emisión</dd>
</dl>
<div class="confidential">
<span>Documento confidencial</span>
<span>Tamaño A4</span>
</div>
</div>
</section>
<!-- ============================= TOC ============================= -->
<section class="toc">
<div class="kick">Contenido</div>
<h2>Índice</h2>
<div class="bar"></div>
<div class="toc-row"><span class="n"></span><span class="t">Resumen ejecutivo</span><span class="dots"></span><span class="pg">{{PG_execsum}}</span></div>
<div class="toc-row section"><span class="n">01</span><span class="t">Entendimiento del proyecto</span><span class="dots"></span><span class="pg">{{PG_1}}</span></div>
<div class="toc-row section"><span class="n">02</span><span class="t">Alcance y requerimientos</span><span class="dots"></span><span class="pg">{{PG_2}}</span></div>
<div class="toc-row section"><span class="n">03</span><span class="t">Inversión y modelo</span><span class="dots"></span><span class="pg">{{PG_3}}</span></div>
<div class="toc-row section"><span class="n">A</span><span class="t">Anexo A — Cobertura por módulo</span><span class="dots"></span><span class="pg">{{PG_anexoA}}</span></div>
</section>
<!-- ============================= BODY ============================= -->
<main class="content">
<section class="sec">
<div class="sec-head no-num"><div><span class="kick">Resumen</span><h2>Resumen ejecutivo</h2></div></div>
<p class="drop">Esta propuesta resume el alcance, la inversión y el modelo de colaboración para construir una plataforma de reservaciones para Náutica del Norte, que hoy gestiona sus reservas por teléfono y hojas de cálculo. El objetivo de la primera etapa es centralizar la disponibilidad, el cobro y la confirmación en una sola herramienta, reduciendo el trabajo manual y los errores de doble reserva.</p>
<div class="synthesis">
<div class="stat"><div class="big">6 sem</div><div class="lab">Plazo estimado</div></div>
<div class="stat"><div class="big">$72K</div><div class="lab">Inversión (MXN)</div></div>
<div class="stat"><div class="big">24</div><div class="lab">Requerimientos</div></div>
</div>
</section>
<section class="sec">
<div class="sec-head"><div class="sec-num">01</div><div><h2>Entendimiento del proyecto</h2></div></div>
<p class="lede">El reto principal del proyecto es eliminar la coordinación manual de reservas sin interrumpir la operación durante temporada alta.</p>
<p>El flujo objetivo conecta la disponibilidad de embarcaciones con el cobro y la confirmación automática al cliente:</p>
<div class="flow">
<span class="chip">Disponibilidad</span><span class="arrow"></span>
<span class="chip">Reserva</span><span class="arrow"></span>
<span class="chip accent">Cobro</span><span class="arrow"></span>
<span class="chip">Confirmación</span><span class="arrow"></span>
<span class="chip">Recordatorio</span>
</div>
<div class="callout cool"><strong>Prioridad de la primera etapa:</strong> disponibilidad, reserva y cobro en línea. La conciliación contable y el programa de lealtad se difieren a fases posteriores.</div>
</section>
<section class="sec">
<div class="sec-head"><div class="sec-num">02</div><div><h2>Alcance y requerimientos</h2></div></div>
<p>El alcance se organiza en módulos. La siguiente matriz lista los requerimientos funcionales de la primera etapa; es deliberadamente extensa para mostrar cómo una tabla larga reparte sus filas entre páginas repitiendo el encabezado.</p>
<table class="compact">
<thead><tr><th style="width:46pt;">ID</th><th>Requerimiento</th><th>Módulo</th><th class="ctr" style="width:54pt;">Prioridad</th></tr></thead>
<tbody>
<tr><td class="k">RF-01</td><td>Calendario de disponibilidad por embarcación</td><td>Reservas</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-02</td><td>Bloqueo de horarios por mantenimiento</td><td>Reservas</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-03</td><td>Reserva con selección de fecha, hora y duración</td><td>Reservas</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-04</td><td>Reglas de anticipación mínima y máxima</td><td>Reservas</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-05</td><td>Cupos y capacidad por embarcación</td><td>Reservas</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-06</td><td>Cobro en línea con tarjeta</td><td>Pagos</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-07</td><td>Cobro de anticipo configurable</td><td>Pagos</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-08</td><td>Reembolsos y cancelaciones con política</td><td>Pagos</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-09</td><td>Comprobante de pago al cliente</td><td>Pagos</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-10</td><td>Confirmación automática por correo</td><td>Notificaciones</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-11</td><td>Recordatorio previo a la reserva</td><td>Notificaciones</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-12</td><td>Notificación de cambios o cancelación</td><td>Notificaciones</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-13</td><td>Registro y autenticación de clientes</td><td>Clientes</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-14</td><td>Historial de reservas del cliente</td><td>Clientes</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-15</td><td>Datos de contacto y preferencias</td><td>Clientes</td><td class="ctr">Baja</td></tr>
<tr><td class="k">RF-16</td><td>Panel de operación con agenda del día</td><td>Operación</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-17</td><td>Reprogramación manual desde el panel</td><td>Operación</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-18</td><td>Lista blanca de clientes frecuentes</td><td>Operación</td><td class="ctr">Baja</td></tr>
<tr><td class="k">RF-19</td><td>Reporte de ocupación por periodo</td><td>Reportes</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-20</td><td>Reporte de ingresos por embarcación</td><td>Reportes</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-21</td><td>Exportación a Excel</td><td>Reportes</td><td class="ctr">Baja</td></tr>
<tr><td class="k">RF-22</td><td>Roles y permisos (admin / operador)</td><td>Seguridad</td><td class="ctr">Alta</td></tr>
<tr><td class="k">RF-23</td><td>Bitácora de cambios</td><td>Seguridad</td><td class="ctr">Media</td></tr>
<tr><td class="k">RF-24</td><td>Respaldo automático de la base de datos</td><td>Seguridad</td><td class="ctr">Alta</td></tr>
<tr class="total"><td>Total</td><td>24 requerimientos en 7 módulos</td><td colspan="2" class="num">Etapa 1</td></tr>
</tbody>
</table>
<div class="callout">La matriz completa de requerimientos no funcionales (rendimiento, seguridad y disponibilidad) se acuerda en el Discovery y se anexa al contrato.</div>
</section>
<section class="sec">
<div class="sec-head"><div class="sec-num">03</div><div><h2>Inversión y modelo</h2></div></div>
<p>El proyecto se entrega bajo un esquema de tiempo y materiales con tope por etapa, a una tarifa de $600 MXN/h + IVA.</p>
<table>
<thead><tr><th>Etapa</th><th>Entregable</th><th class="num">Horas</th><th class="num">Inversión (MXN)</th></tr></thead>
<tbody>
<tr><td class="k">0</td><td>Discovery y arquitectura</td><td class="num">20</td><td class="num">$12,000</td></tr>
<tr><td class="k">1</td><td>Reservas y disponibilidad</td><td class="num">36</td><td class="num">$21,600</td></tr>
<tr><td class="k">2</td><td>Pagos y notificaciones</td><td class="num">34</td><td class="num">$20,400</td></tr>
<tr><td class="k">3</td><td>Operación, reportes y cierre</td><td class="num">30</td><td class="num">$18,000</td></tr>
<tr class="total"><td>Total</td><td>Primera etapa</td><td class="num">120 h</td><td class="num">$72,000</td></tr>
</tbody>
</table>
<div class="signoff">
<div class="nm">Johann Velázquez</div>
<div class="rl">Consultor de Software · Monterrey, Nuevo León</div>
</div>
</section>
<section class="sec break">
<div class="sec-head"><div class="sec-num">A</div><div><span class="kick">Anexo</span><h2>Cobertura por módulo</h2></div></div>
<p class="lede">Este anexo detalla la cobertura de cada módulo en la primera etapa.</p>
<table>
<thead><tr><th>Módulo</th><th class="ctr" style="width:62pt;">Cobertura</th><th>Detalle</th></tr></thead>
<tbody>
<tr><td class="k">Reservas</td><td class="cov"></td><td>Disponibilidad, reserva, cupos y reglas de anticipación.</td></tr>
<tr><td class="k">Pagos</td><td class="cov"></td><td>Cobro con tarjeta, anticipo y comprobante.</td></tr>
<tr><td class="k">Conciliación contable</td><td class="cov"></td><td>Diferida a fase posterior.</td></tr>
<tr><td class="k">Lealtad</td><td class="cov"></td><td>Diferida a fase posterior.</td></tr>
</tbody>
</table>
</section>
</main>
</body>
</html>
@@ -0,0 +1,459 @@
#!/usr/bin/env python3
"""
build_pdf.py — print-optimized PDF builder for editorial proposals/reports.
Pipeline (the reliable part — don't reinvent it):
1. Render the authored HTML to PDF with headless Chromium (Playwright),
letting CSS @page rules control the page geometry.
2. Resolve the table-of-contents page numbers in a SECOND pass: render once
with the {{PG_*}} tokens blanked, find the real start page of each section
by searching the rendered PDF for a unique body phrase, substitute the
numbers, then re-render.
3. Stamp a running footer (hairline + confidential line + page number) on
every page except the cover, using a registered TTF.
4. Write PDF metadata (title / author / subject).
Why two passes instead of computing pages from the DOM: with CSS paged media,
the mapping from DOM position to printed page is not linear (page margins and
break-before rules eat space). Searching the actually-rendered PDF sidesteps
all of it and is rock-solid.
Usage:
python3 build_pdf.py [config.json] # build
python3 build_pdf.py [config.json] --check # validate config + env only
If no config path is given it looks for ./pdf.config.json.
See pdf.config.example.json for the full schema.
"""
import sys, os, re, json, io, tempfile, pathlib
# Windows consoles default to cp1252 and choke on the ✓/⚠/✗ status glyphs this
# script prints. Force UTF-8 on stdout/stderr so progress output never crashes.
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8")
except Exception:
pass
# --- friendly dependency check -------------------------------------------- #
_MISSING = []
try:
from playwright.sync_api import sync_playwright
except Exception:
_MISSING.append("playwright")
try:
import pdfplumber
except Exception:
_MISSING.append("pdfplumber")
try:
import pypdf
except Exception:
_MISSING.append("pypdf")
try:
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.colors import Color
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont as RLTTFont
except Exception:
_MISSING.append("reportlab")
if _MISSING:
sys.exit(
"Missing Python packages: " + ", ".join(_MISSING) + "\n"
"Install with:\n"
" pip install playwright pdfplumber pypdf reportlab\n"
" python -m playwright install chromium"
)
# --------------------------------------------------------------------------- #
# Page geometry
# --------------------------------------------------------------------------- #
# Named sizes in points (1pt = 1/72in). Used for the footer overlay canvas so
# it matches whatever Chromium printed.
PAGE_SIZES_PT = {
"letter": (612.0, 792.0),
"legal": (612.0, 1008.0),
"a4": (595.28, 841.89),
"a3": (841.89, 1190.55),
}
def resolve_page_size(page_size):
"""Return (playwright_format_or_None, width_pt, height_pt, width_mm, height_mm).
page_size may be a string ("Letter"/"A4"/...) or a dict
{"width_mm": .., "height_mm": ..} for a custom size.
"""
if isinstance(page_size, dict):
wmm = float(page_size["width_mm"]); hmm = float(page_size["height_mm"])
return None, wmm * 72 / 25.4, hmm * 72 / 25.4, wmm, hmm
key = str(page_size).strip().lower()
if key not in PAGE_SIZES_PT:
sys.exit(f"Unknown page_size {page_size!r}. Use one of "
f"{sorted(PAGE_SIZES_PT)} or a {{width_mm,height_mm}} object.")
wpt, hpt = PAGE_SIZES_PT[key]
return key.capitalize(), wpt, hpt, wpt * 25.4 / 72, hpt * 25.4 / 72
# --------------------------------------------------------------------------- #
# Config
# --------------------------------------------------------------------------- #
def load_config(path):
p = pathlib.Path(path)
if not p.exists():
sys.exit(f"Config not found: {path}\nCopy pdf.config.example.json and edit it.")
try:
cfg = json.loads(p.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
sys.exit(f"Config is not valid JSON ({path}): {e}")
cfg.setdefault("metadata", {})
cfg.setdefault("toc", {})
cfg.setdefault("footer", {})
f = cfg["footer"]
f.setdefault("skip_first_page", True)
f.setdefault("skip_pages", [])
f.setdefault("rule", True)
f.setdefault("font_size_pt", 7.5)
f.setdefault("margin_mm", 12) # distance from the bottom edge
f.setdefault("color", [0.46, 0.51, 0.57])
f.setdefault("page_number_format", "{page}")
cfg.setdefault("fonts", {})
cfg.setdefault("cover_full_bleed", True)
cfg.setdefault("page_size", "Letter")
cfg.setdefault("render_timeout_ms", 30000)
base = p.resolve().parent
for key in ("input_html", "output_pdf"):
if key in cfg and not os.path.isabs(cfg[key]):
cfg[key] = str(base / cfg[key])
ttf = cfg["fonts"].get("footer_ttf")
if ttf and not os.path.isabs(ttf):
cand = base / ttf
# only rewrite to a config-relative path if that file actually exists;
# otherwise leave the original (likely an absolute system font path)
if cand.exists():
cfg["fonts"]["footer_ttf"] = str(cand)
return cfg
# --------------------------------------------------------------------------- #
# Token / anchor helpers
# --------------------------------------------------------------------------- #
TOKEN_RE = re.compile(r"\{\{PG_([^}]+)\}\}")
def _norm(s):
return re.sub(r"\s+", " ", s or "")
def strip_comments(html):
# HTML comments never belong in the rendered PDF, and any {{PG_*}} examples
# inside them must not be treated as real tokens.
return re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL)
def tokens_in_html(html):
return set(TOKEN_RE.findall(html))
def blank_tokens(html):
return TOKEN_RE.sub("", html)
def fill_tokens(html, pages):
def repl(m):
key = m.group(1)
if key not in pages:
sys.exit(f"Token {{{{PG_{key}}}}} has no matching entry in config 'toc'.")
return str(pages[key])
return TOKEN_RE.sub(repl, html)
# --------------------------------------------------------------------------- #
# Preflight validation
# --------------------------------------------------------------------------- #
def chromium_ok():
try:
with sync_playwright() as p:
b = p.chromium.launch()
b.close()
return True, ""
except Exception as e:
return False, str(e).splitlines()[0]
def preflight(cfg, html):
"""Validate config + environment. Returns list of warning strings; exits on
hard errors."""
problems, warnings = [], []
# input html
if not os.path.exists(cfg.get("input_html", "")):
problems.append(f"input_html not found: {cfg.get('input_html')!r}")
# token <-> toc consistency
toks = tokens_in_html(html)
toc_keys = {k for k in cfg["toc"] if not k.startswith("_")}
missing_cfg = toks - toc_keys # token in HTML, no anchor in config
unused_cfg = toc_keys - toks # anchor in config, no token in HTML
if missing_cfg:
problems.append("TOC tokens in the HTML with no anchor in config 'toc': "
+ ", ".join(sorted("{{PG_%s}}" % k for k in missing_cfg)))
if unused_cfg:
warnings.append("config 'toc' keys with no matching {{PG_*}} token in the HTML: "
+ ", ".join(sorted(unused_cfg)))
# footer font
ttf = cfg["fonts"].get("footer_ttf")
if ttf and not os.path.exists(ttf):
warnings.append(f"footer_ttf not found ({ttf}); falling back to Helvetica.")
# output dir
out = cfg.get("output_pdf")
if out:
os.makedirs(os.path.dirname(os.path.abspath(out)), exist_ok=True)
# chromium
ok, err = chromium_ok()
if not ok:
problems.append("Chromium failed to launch: " + err
+ "\n Run: python -m playwright install chromium")
if problems:
sys.exit("Preflight failed:\n" + "\n".join("" + p for p in problems))
return warnings
# --------------------------------------------------------------------------- #
# 1. Render
# --------------------------------------------------------------------------- #
def render(html_text, out_pdf, pw_format, width_pt, height_pt, timeout_ms, base_dir=None):
# Write the temp HTML in the SAME directory as the source document so that
# relative resources in the HTML — @font-face url("fonts/..."), <img src>,
# CSS background images — resolve against the document folder. (Rendering
# from the system temp dir would break every relative path.)
with tempfile.NamedTemporaryFile("w", prefix=".pdfbuild_", suffix=".html",
delete=False, encoding="utf-8", dir=base_dir) as fh:
fh.write(html_text)
tmp_path = fh.name
try:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.set_default_timeout(timeout_ms)
uri = pathlib.Path(tmp_path).resolve().as_uri()
# networkidle is ideal but can hang on a stuck resource; fall back to
# 'load' so a self-contained document always renders.
try:
page.goto(uri, wait_until="networkidle", timeout=timeout_ms)
except Exception:
page.goto(uri, wait_until="load", timeout=timeout_ms)
page.emulate_media(media="print")
pdf_kwargs = dict(
path=out_pdf,
print_background=True,
# margin 0 here so the CSS @page rules are the single source of
# truth — including @page:first{margin:0} for a full-bleed cover.
margin={"top": "0", "right": "0", "bottom": "0", "left": "0"},
prefer_css_page_size=True,
)
if pw_format:
pdf_kwargs["format"] = pw_format
else: # custom size
pdf_kwargs["width"] = f"{width_pt}pt"
pdf_kwargs["height"] = f"{height_pt}pt"
page.pdf(**pdf_kwargs)
browser.close()
finally:
os.unlink(tmp_path)
# --------------------------------------------------------------------------- #
# 2. TOC page-number resolution
# --------------------------------------------------------------------------- #
def detect_pages(pdf_path, anchors):
"""Map each TOC key to the 1-based page where its anchor phrase first appears."""
with pdfplumber.open(pdf_path) as pdf:
page_texts = [_norm(pg.extract_text() or "") for pg in pdf.pages]
pages, missing, ambiguous = {}, [], []
for key, phrase in anchors.items():
if key.startswith("_"):
continue
target = _norm(phrase)
hits = [i + 1 for i, txt in enumerate(page_texts) if target in txt]
if not hits:
missing.append((key, phrase))
else:
pages[key] = hits[0]
if len(hits) > 1:
ambiguous.append((key, hits))
if missing:
lines = "\n".join(f" - {k!r}: {p!r}" for k, p in missing)
sys.exit(
"Could not locate these TOC anchors in the rendered PDF.\n"
"Use a UNIQUE phrase from the section BODY (not its title), and avoid\n"
"the first letter of a drop-cap paragraph:\n" + lines
)
for k, hits in ambiguous:
print(f" ! anchor {k!r} appears on pages {hits}; using the first ({hits[0]}). "
"Use a more specific phrase if that's wrong.")
return pages
# --------------------------------------------------------------------------- #
# 3. Footer stamping + 4. Metadata
# --------------------------------------------------------------------------- #
def register_footer_font(ttf_path):
if ttf_path and os.path.exists(ttf_path):
try:
pdfmetrics.registerFont(RLTTFont("FooterFont", ttf_path))
return "FooterFont"
except Exception:
pass
return "Helvetica"
def make_overlay(page_number, total_pages, fcfg, font_name, width_pt, height_pt):
buf = io.BytesIO()
c = canvas.Canvas(buf, pagesize=(width_pt, height_pt))
left = 16 * mm
right_x = width_pt - 16 * mm
y_text = fcfg["margin_mm"] * mm
y_rule = y_text + 3.2 * mm
col = Color(*fcfg["color"])
if fcfg.get("rule", True):
c.setStrokeColor(Color(0.80, 0.83, 0.86))
c.setLineWidth(0.5)
c.line(left, y_rule, right_x, y_rule)
c.setFillColor(col)
c.setFont(font_name, float(fcfg["font_size_pt"]))
text = fcfg.get("text", "")
if text:
c.drawString(left, y_text, text)
num = fcfg.get("page_number_format", "{page}").format(page=page_number, pages=total_pages)
if num:
c.drawRightString(right_x, y_text, num)
c.showPage()
c.save()
buf.seek(0)
return pypdf.PdfReader(buf).pages[0]
def stamp_and_finalize(src_pdf, out_pdf, fcfg, font_name, metadata, width_pt, height_pt):
reader = pypdf.PdfReader(src_pdf)
writer = pypdf.PdfWriter()
total = len(reader.pages)
skip_first = fcfg.get("skip_first_page", True)
skip_pages = set(fcfg.get("skip_pages", []))
for idx, page in enumerate(reader.pages):
page_no = idx + 1
skip = (page_no in skip_pages) or (idx == 0 and skip_first)
if not skip:
page.merge_page(make_overlay(page_no, total, fcfg, font_name, width_pt, height_pt))
writer.add_page(page)
meta = {}
if metadata.get("title"): meta["/Title"] = metadata["title"]
if metadata.get("author"): meta["/Author"] = metadata["author"]
if metadata.get("subject"): meta["/Subject"] = metadata["subject"]
if metadata.get("author"): meta["/Creator"] = metadata["author"]
if metadata.get("keywords"): meta["/Keywords"] = metadata["keywords"]
if meta:
writer.add_metadata(meta)
with open(out_pdf, "wb") as fh:
writer.write(fh)
# --------------------------------------------------------------------------- #
# Post-build: warn if the design fonts didn't make it into the PDF
# --------------------------------------------------------------------------- #
# The design system is built on Caladea (serif display) + Carlito (sans body).
# If those aren't embedded, Chromium silently fell back to system fonts
# (Cambria/Calibri/Georgia/…) — it *looks close* but isn't the intended result,
# exactly the kind of regression that ships unnoticed. Flag it loudly.
DESIGN_FONTS = ("Caladea", "Carlito")
def report_embedded_fonts(pdf_path):
try:
reader = pypdf.PdfReader(pdf_path)
names = set()
for pg in reader.pages:
res = pg.get("/Resources")
fonts = res.get("/Font") if res else None
if not fonts:
continue
fobj = fonts.get_object()
for fk in fobj:
bf = fobj[fk].get_object().get("/BaseFont")
if bf:
names.add(str(bf).lstrip("/").split("+")[-1]) # drop subset prefix
except Exception:
return # a reporting step must never fail the build
present = sorted(names)
print(" fonts embedded:", ", ".join(present) if present else "(none)")
missing = [f for f in DESIGN_FONTS if not any(f in n for n in names)]
if missing:
print(" ⚠ design font(s) missing from the PDF: " + ", ".join(missing) + ".")
print(" Chromium fell back to system fonts, so the result looks")
print(" 'close but not identical' to the intended design. Ensure the")
print(" .ttf files sit next to the HTML (./fonts/) and the @font-face")
print(" url() paths resolve. See SKILL.md Fonts.")
# --------------------------------------------------------------------------- #
# Orchestration
# --------------------------------------------------------------------------- #
def main():
args = [a for a in sys.argv[1:]]
check_only = "--check" in args
args = [a for a in args if a != "--check"]
cfg_path = args[0] if args else "pdf.config.json"
cfg = load_config(cfg_path)
raw_html = pathlib.Path(cfg["input_html"]).read_text(encoding="utf-8") \
if os.path.exists(cfg["input_html"]) else ""
src_html = strip_comments(raw_html)
warnings = preflight(cfg, src_html)
for w in warnings:
print("" + w)
if check_only:
print("Preflight OK." + (" (with warnings)" if warnings else ""))
return
pw_format, width_pt, height_pt, _, _ = resolve_page_size(cfg["page_size"])
timeout_ms = int(cfg["render_timeout_ms"])
# render relative resources (fonts/images) against the document's folder
base_dir = os.path.dirname(os.path.abspath(cfg["input_html"])) or None
with tempfile.TemporaryDirectory() as td:
v1 = os.path.join(td, "v1.pdf")
v2 = os.path.join(td, "v2.pdf")
if any(not k.startswith("_") for k in cfg["toc"]):
print("Pass 1/2: resolving TOC page numbers…")
render(blank_tokens(src_html), v1, pw_format, width_pt, height_pt, timeout_ms, base_dir)
pages = detect_pages(v1, cfg["toc"])
print(" resolved:", pages)
html_final = fill_tokens(src_html, pages)
else:
html_final = blank_tokens(src_html)
print("Pass 2/2: rendering final document…")
render(html_final, v2, pw_format, width_pt, height_pt, timeout_ms, base_dir)
font_name = register_footer_font(cfg["fonts"].get("footer_ttf"))
stamp_and_finalize(v2, cfg["output_pdf"], cfg["footer"], font_name,
cfg["metadata"], width_pt, height_pt)
n = len(pypdf.PdfReader(cfg["output_pdf"]).pages)
print(f"✓ Wrote {cfg['output_pdf']} ({n} pages, {cfg['page_size']})")
report_embedded_fonts(cfg["output_pdf"])
if __name__ == "__main__":
main()
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""render_check.py — rasterize PDF pages to PNG for visual verification.
Cross-platform: uses pypdfium2 (already pulled in with pdfplumber), so it needs
no poppler/pdftoppm. On Windows this is the practical way to do the "ALWAYS
verify visually" step from SKILL.md.
Usage:
python render_check.py Output.pdf # every page -> Output_p1.png, ...
python render_check.py Output.pdf 1 2 5 # only pages 1, 2, 5 (1-based)
python render_check.py Output.pdf 1 2 --scale 2 # higher resolution
"""
import sys, pathlib
try:
import pypdfium2 as pdfium
except Exception:
sys.exit("Missing pypdfium2 (it installs alongside pdfplumber).\n"
" pip install pypdfium2")
def main():
args = list(sys.argv[1:])
if not args:
sys.exit("Usage: python render_check.py <pdf> [pages...] [--scale N]")
scale = 1.5
if "--scale" in args:
i = args.index("--scale")
try:
scale = float(args[i + 1])
except (IndexError, ValueError):
sys.exit("--scale needs a number, e.g. --scale 2")
del args[i:i + 2]
pdf_path = args[0]
if not pathlib.Path(pdf_path).exists():
sys.exit(f"PDF not found: {pdf_path}")
pages = [int(a) for a in args[1:]]
pdf = pdfium.PdfDocument(pdf_path)
n = len(pdf)
idxs = [p - 1 for p in pages] if pages else range(n)
stem = pathlib.Path(pdf_path).with_suffix("")
for i in idxs:
if i < 0 or i >= n:
print(f" ! page {i + 1} out of range (1..{n})")
continue
out = f"{stem}_p{i + 1}.png"
pdf[i].render(scale=scale).to_pil().save(out)
print("wrote", out)
if __name__ == "__main__":
main()
+12
View File
@@ -3,3 +3,15 @@ Thumbs.db
*.swp
.vscode/
.idea/
# Python (skill proposal-pdf)
__pycache__/
*.pyc
# Archivos temporales de Office (locks de Excel/Word)
~$*
# proposal-pdf: artefactos transitorios (NO las fuentes de fonts/, que sí se versionan)
.pdfbuild_*.html # HTML temporal que escribe build_pdf.py al renderizar
*_p[0-9].png # PNGs de render_check.py (verificación visual)
*_p[0-9][0-9].png
-472
View File
@@ -1,472 +0,0 @@
# Propuesta Comercial — Plataforma de Automatización Financiera (Balam)
**Para:** Balam
**De:** [Tu nombre]
**Fecha:** Mayo 2026
**Vigencia:** 30 días
---
## 1. Entendimiento del proyecto
### El problema, en palabras del equipo directivo
> *"El proceso está tan desvinculado… pasa por varias manos… cada parte humana se está equivocando."*
Esto es lo que escuché en la llamada con CEO, CFO y CTO: el dolor no es técnico, es operativo y costoso. Cada handoff manual entre nómina, facturación, cobranza, conciliación y contabilidad introduce error humano que se paga en dinero perdido, retrabajo y deterioro de relación con clientes estratégicos.
### Lo que existe hoy
- **BIND ERP** (facturación + contabilidad con PAC integrado)
- **BUK** (HR, contratos, nómina, vacaciones — core de gestión de talento)
- **Jira** (gestión de proyectos y servicios con clientes)
- **3 bancos** en PDF (2 MX + IBC Bank Texas)
Estos sistemas funcionan individualmente, pero no conversan entre sí. La **operación financiera vive en el espacio entre ellos** — y hoy ese espacio se llena con trabajo manual y propenso a error.
### Lo que propongo construir
Una **capa de operaciones financieras encima de los sistemas existentes**, no un reemplazo. BIND sigue siendo fuente de verdad para facturación y contabilidad; BUK sigue siendo el core de HR; Jira sigue gestionando proyectos. La plataforma orquesta el flujo financiero que hoy nadie tiene:
**MVP (6 semanas):**
- Cobranza automatizada con reglas de negocio (lista blanca ACUNTIA + top 3)
- Conciliación bancaria sobre PDFs (3 bancos, incluyendo IBC Texas) usando IA para parsing
- Dashboard consolidado de CxC y vencimientos
- Pago con link Stripe — nuevo requerimiento confirmado
- Alertas y reportes programados
**Visión end-to-end (Fase 2-3):**
La arquitectura del MVP queda preparada para cerrar el ciclo completo que el equipo directivo describió en la llamada:
```
Jira (horas) → BUK (nómina) → Plataforma → Factura → Cobranza → Conciliación → Asiento
```
Cuando BIND y BUK habiliten APIs (o decidan invertir en Belvo / Plaid / conectores), la plataforma absorbe esos nuevos disparadores sin reescribir lo construido. Hoy importamos archivos; mañana escuchamos webhooks. El motor de orquestación es el mismo.
### El éxito del proyecto se mide en
- **Reducción ≥ 70%** de errores manuales contables
- **Reducción ≥ 60%** del tiempo de conciliación bancaria
- **Visibilidad en tiempo real** del estado financiero para dirección
- **MVP funcional** con flujo end-to-end de cobranza + conciliación + pago en 6 semanas
- **Cero riesgo** sobre la relación con clientes estratégicos (lista blanca dura)
### Escala operativa (referencia)
- 45 colaboradores en nómina + 5 freelancers
- ~50 facturas emitidas al mes
- 3 bancos (2 MX + IBC Bank Texas)
- 2 jurisdicciones fiscales (México + Texas, tax estándar en MVP)
- Stakeholders operativos: contacto técnico + gerente administrativo (con visibilidad ejecutiva de CEO/CFO/CTO)
---
## 2. Modelo de colaboración
### 2.1 Modalidad: Time & Materials con presupuesto por fase
Trabajo bajo modelo de **horas reales con tarifa transparente**, no precio cerrado. Razón: el alcance real de las integraciones (BIND, bancos, SAT) sólo se conoce al inspeccionar APIs y datos en vivo; un precio fijo obligaría a inflar el presupuesto para cubrir incertidumbre, encareciendo el proyecto innecesariamente.
A cambio, ofrezco:
- **Rango estimado por fase** (mín-máx en horas) acordado por escrito antes de iniciar cada fase
- **Soft cap por fase**: si proyecto rebasar el rango máximo, paro y conversamos antes de continuar — nunca hay "sorpresas" en factura
- **Reporte semanal** de horas trabajadas con desglose por tarea (vía Notion / Linear / sheet compartido)
- **Demo semanal** del avance entregado
### 2.2 Toolchain moderno (Claude Code + revisión humana)
Trabajo con un toolchain de desarrollo asistido por IA (Claude Code de Anthropic) como acelerador. **Esto es información que Balam debe saber por transparencia y por compliance**:
- **Beneficio para Balam:** ranges de horas estimados arriba son conservadores; con esta metodología tienden al **extremo bajo del rango** sin pérdida de calidad. Mayor cobertura de tests, mejor documentación y refactor más frecuente quedan dentro del mismo presupuesto.
- **Responsabilidad humana:** todo código entregado es revisado y validado por mí. La IA es asistente, no es quien firma el código. Yo soy responsable de cada línea que se mergea a `main`.
- **Datos de Balam quedan fuera:** ningún dato real de Balam (clientes, montos, credenciales, XMLs reales, estados de cuenta) se envía a modelos externos. Se trabaja con datos sintéticos generados a partir de la estructura, no del contenido. Las credenciales viven en gestores de secretos cifrados; nunca se pegan en prompts.
- **Compliance:** la metodología cumple con la política habitual de proveedores que manejan datos financieros (no entrenamiento sobre datos del cliente, no telemetría de datos del cliente, código en repositorio del cliente desde día 1).
- **Auditabilidad:** las decisiones de arquitectura quedan registradas en ADRs versionados (`docs/adr/`), exactamente como si fueran de un humano.
### 2.3 Dedicación: medio tiempo
Trabajaré **medio tiempo (~20 h/semana)** sobre el proyecto. El calendario de 6 semanas asume esa dedicación; con eso el budget total es de **110140 horas**.
### 2.4 Tarifa
**600 MXN / hora trabajada** + IVA (facturación CFDI 4.0).
### 2.5 Facturación
- **Semanal**, los viernes, por las horas trabajadas la semana anterior
- Pago a 7 días naturales vía transferencia
- Cada factura incluye anexo con detalle de horas por tarea/fase
### 2.6 Comunicación
- **Standup async 2-3 veces por semana** (texto + Loom corto en Slack/WhatsApp) — qué hice, qué sigue, bloqueadores
- **Demo semanal** (30 min, viernes) con el contacto técnico y el gerente administrativo
- **Sesión técnica quincenal** (1 h) — decisiones de arquitectura, validación de reglas de negocio
- Disponibilidad para llamadas urgentes con 24 h de aviso en horario laboral (9-18 hrs CST)
---
## 3. Alcance — MVP en 6 semanas (medio tiempo)
> **Filosofía:** Un MVP entrega valor real lo antes posible con el alcance **mínimo viable**, no con todo lo deseable. Lo que no entre en estas 6 semanas vive en el roadmap de Fase 2 (§4) y se cotiza al cerrar el MVP, con datos reales de uso que justifiquen las prioridades.
> **Posicionamiento:** la plataforma es una **capa de operaciones financieras encima de BIND**, no un reemplazo. BIND sigue siendo la fuente de verdad para facturación, timbrado CFDI y contabilidad. La plataforma orquesta cobranza, conciliación, dashboard, pagos con link y alertas.
### Fase 0 — Discovery + Setup *(Semana 1 · 18 22 h)*
**Objetivo:** Validar supuestos técnicos críticos (BIND, PDFs bancarios, Stripe), preparar la infraestructura Azure y dejar el repositorio listo.
**Entregables:**
- Inspección del formato de export de BIND (estructura del archivo, frecuencia, contenido — facturas, clientes, catálogo)
- Recolección de muestras reales (anonimizadas) de PDFs de los 3 bancos
- Validación de viabilidad de parsing con Claude API sobre 2-3 PDFs reales por banco
- Cuenta Azure creada con recursos base (App Service + Postgres + Storage) y staging vacío
- Repositorio monorepo + CI/CD + workflow Claude Code configurado con guardrails de seguridad
- Cuenta Stripe MX creada y validada
- Documento de supuestos validados + plan refinado de Fases 1-4
- Manual de marca recibido y aplicado a maquetas iniciales
---
### Fase 1 — Plataforma base + Sincronización BIND *(Semanas 2-3 · 30 38 h)*
**Objetivo:** Plataforma funcional que ingiere facturas de BIND, mantiene catálogo de clientes y rastrea estatus.
**Entregables:**
- Backend con autenticación, roles (Finanzas / Dirección / Operaciones / Admin), audit log universal
- Multi-tenancy en schema (`tenant_id` + RLS) — preparado sin onboarding self-service
- Importador de archivo de BIND (clientes, facturas, productos) con dedupe y validación
- Modelo central de facturas con estados (Emitida / Pagada / Vencida / Cancelada)
- Visualización multimoneda **MXN y USD** con TC diario del DOF (snapshot al momento de la factura)
- UI: listado de facturas con filtros, búsqueda, drill-down a detalle
- Gestión de catálogo de clientes con bandera `auto_reminder_enabled` para lista blanca
- Branding aplicado (logo + colores del manual de marca)
- Manual de usuario v1
> **Importante:** la plataforma **no emite CFDI**; eso lo sigue haciendo BIND con su PAC integrado. Si en el futuro Balam quiere emitir desde la plataforma, se evalúa en Fase 2 (requiere contratar PAC adicional o desarrollar conector contra BIND).
> **Fuera de MVP:** EUR, integración BUK, sincronización en vivo con BIND (mientras BIND no exponga API, se trabaja con export programado).
---
### Fase 2 — Cobranza + Dashboard + Pago con link *(Semana 4 · 25 32 h)*
**Objetivo:** Cobranza semi-automatizada con visibilidad real y un canal moderno de cobro.
**Entregables:**
- Registro manual de pagos + asociación a facturas (totales y parciales)
- Editor de template para correos de cobranza (markdown + variables)
- Motor de recordatorios automáticos por email (cron + worker idempotente)
- **Lista blanca dura:** ACUNTIA + Top 3 jamás reciben recordatorio automático (configurable)
- Recordatorio 5 días antes del vencimiento (configurable)
- Log de comunicaciones enviadas con UI de revisión
- **Pago con link (Stripe Checkout):**
- Generación de link de pago por factura
- Envío del link junto con el recordatorio de cobranza
- Webhook que marca factura como pagada al recibir confirmación de Stripe
- Soporte para tarjeta y SPEI vía Stripe
- Dashboard v1: CxC totales y por cliente, vencidas, próximas a vencer, exportación a Excel
> **Fuera de MVP:** domiciliación / pagos recurrentes (Stripe Subscriptions o equivalente, requiere onboarding del cliente y manejo de mandatos — se cotiza en Fase 2). Cash-flow proyectado 30/60/90.
---
### Fase 3 — Conciliación bancaria por PDF *(Semana 5 · 22 28 h)*
**Objetivo:** Eliminar conciliación manual sobre los 3 PDFs bancarios.
**Entregables:**
- Importación de estados de cuenta en PDF (upload manual por el equipo)
- **Extracción estructurada vía Claude API** (movimientos: fecha, monto, concepto, referencia)
- Una pipeline por banco con prompts validados sobre muestras reales (incluyendo IBC Bank Texas)
- Validación de totales (suma de movimientos vs. resumen del PDF) para detectar extracciones incorrectas
- Dedupe por hash del registro extraído
- Motor de conciliación:
- Match exacto (monto + referencia + ventana de fecha) → automático
- Match por alias de cliente → automático con flag de revisión
- Sin match → cola de revisión humana
- Detección básica de duplicados y traspasos internos entre cuentas propias
- Dashboard de movimientos no conciliados
> **Por qué Claude API para parsing:** los formatos de PDF varían por banco y dentro del mismo banco a lo largo del tiempo; un parser hardcodeado por banco rompe con cada cambio. Con Claude se genera salida estructurada robusta a variaciones, con costo ~$0.01-0.05 USD por página y validación automática contra totales. Anthropic API por default no entrena con los datos enviados; los PDFs se procesan con datos reales bajo este compromiso contractual.
> **Fuera de MVP:** integración en vivo con bancos (Belvo / Plaid), detección de anomalías avanzada (z-score, vendor nuevo, horarios atípicos), scraping automatizado del portal de IBC.
---
### Fase 4 — Asientos contables + Cierre del MVP *(Semana 6 · 15 20 h)*
**Objetivo:** Cerrar el flujo de operaciones con export para BIND y entrega formal.
**Entregables:**
- Reporte de pagos conciliados exportable a Excel/CSV en formato compatible con BIND (para carga manual por el contador)
- Reporte mensual de CxC y CxP
- Backups automáticos + plan de recuperación documentado
- Endurecimiento de seguridad final (helmet, rate limiting, RBAC granular, modo dry-run para operaciones contra BIND)
- Documentación técnica: README, runbook operativo, ADRs
- Sesión de capacitación grabada (1.5-2 h) con contacto técnico y gerente administrativo
- Handoff + soporte post-lanzamiento de 2 semanas (corrección de bugs)
> **Importante:** los asientos contables completos los **sigue generando BIND**. La plataforma exporta pagos conciliados y movimientos clasificados que el contador sube a BIND. Generación de asientos automáticos contra BIND vía API queda para Fase 2 (depende de que BIND habilite API).
---
### Resumen de estimaciones
| Fase | Entregable principal | Rango (horas) | Rango (MXN) |
|---|---|---|---|
| 0 | Discovery + Setup Azure | 18 22 | $10,800 $13,200 |
| 1 | Plataforma + Sync con BIND | 30 38 | $18,000 $22,800 |
| 2 | Cobranza + Dashboard + Pago link | 25 32 | $15,000 $19,200 |
| 3 | Conciliación PDF (Claude API) | 22 28 | $13,200 $16,800 |
| 4 | Reportes + cierre | 15 20 | $9,000 $12,000 |
| **Total MVP** | | **110 140 horas** | **$66,000 $84,000** |
> Cifras antes de IVA. Tiempo calendario: **6 semanas** con dedicación medio tiempo (~20 h/semana, con flex hasta 25 h en semanas pico).
> Por el uso de toolchain asistido por IA (ver §2.2), la expectativa real es caer cerca del **extremo bajo** de cada rango, salvo sorpresas en parsing de PDFs bancarios o exports de BIND.
---
## 4. Fase 2 (post-MVP) — Roadmap propuesto
No incluido en esta propuesta; se cotiza al cerrar MVP, priorizando con base en lo que se observe en uso real.
**Cerrar el flujo end-to-end (la visión original del equipo directivo)**
- **Jira → horas por colaborador-cliente** ingestadas a la plataforma
- **BUK → nómina aprobada** como trigger automático de factura al cliente
- **Plataforma → BIND**: generación automática de factura tras evento de nómina (caso de uso #1 del PRD inicial)
- **Plataforma → BIND**: asientos contables automáticos tras conciliación
- Ciclo completo: `Jira → BUK → Factura → Cobranza → Conciliación → Asiento`
**Integraciones bancarias y ERP**
- Integración en vivo con bancos vía Belvo (MX) — elimina la carga manual de PDFs
- Plaid u OCR automatizado para IBC Bank si se valida que el cliente puede compartir credenciales
- Conector contra BIND cuando habilite API (eliminar export/import manual)
- Integración con BUK (nómina, contratos, freelancers) cuando exponga API
- Integración con Jira para horas trabajadas → input de facturación
**Funcionalidad financiera**
- Soporte de EUR (clientes europeos)
- Cash-flow proyectado a 30/60/90 días
- Manejo automático de pérdidas/ganancias cambiarias
- **Domiciliación y pagos recurrentes** vía Stripe Subscriptions (pólizas, mensualidades)
- Detección avanzada de anomalías (z-score, vendor nuevo, horarios atípicos, frecuencia)
- Compliance avanzado para operación en Texas (más allá de tax estándar)
**Plataforma y producto**
- Agentes IA / LLM para clasificación de transacciones y sugerencias de match
- Portal de cliente self-service (consulta de facturas, descargas, historial de pagos)
- App móvil para captura de tickets físicos
- Multi-tenancy comercial (onboarding self-service, billing) para comercializar como producto
- Compliance avanzado: NOM-151, evidencia digital, auditoría fiscal Texas
---
## 5. Costos operativos (a cargo de Balam)
Estos no son honorarios míos; son servicios de terceros que la plataforma requiere:
| Servicio | Concepto | Costo estimado |
|---|---|---|
| Azure App Service (Linux, B1/B2) | App + worker | $13 $55 USD/mes |
| Azure Database for PostgreSQL | Base de datos productiva | $25 $80 USD/mes |
| Azure Blob Storage | PDFs bancarios, exports | $1 $5 USD/mes |
| Sentry + uptime monitoring | Observabilidad | $0 $26 USD/mes |
| Email transaccional (Resend) | Recordatorios + links de pago | $0 $20 USD/mes |
| **Claude API** (parsing de PDFs bancarios) | ~3 PDFs/mes × ~20 págs c/u | $5 $20 USD/mes |
| **Stripe** (pago con link) | Comisión por transacción | 3.6% + $3 MXN por pago tarjeta · ~$3 MXN por SPEI |
| **PAC para CFDI** | Ya incluido en BIND | $0 — no costo adicional |
| **Total mensual MVP** | | **$45 $210 USD/mes + comisiones Stripe** |
> En Fase 2, al activar integración bancaria en vivo se suma Belvo ($200 $500 USD/mes) y/o Plaid ($0.30 $1 USD por cuenta/mes).
> Costos de Azure pueden optimizarse evaluando reserved instances después de validar consumo real.
---
## 6. Supuestos críticos
Si alguno no se cumple, replanteamos esa fase:
1. **Export programable o periódico de BIND** disponible (facturas + clientes) en Fase 0; formato documentable.
2. **PDFs reales (anonimizados)** de los 3 bancos compartidos al inicio de Fase 0 para validar parsing.
3. **Acceso al portal de IBC Bank Texas** para que el equipo descargue PDFs manualmente y los suba a la plataforma.
4. **PAC de BIND maneja los volúmenes** actuales y futuros sin costo adicional; la plataforma no timbra facturas en MVP.
5. **Cuenta Azure de Balam** (o autorización para crearla a nombre de Balam) disponible al inicio de Fase 0.
6. **Cuenta Stripe MX** verificada (RFC + datos bancarios) antes de iniciar Fase 2.
7. **Manual de marca** compartido al inicio de Fase 0.
8. **Reglas de negocio finales** validadas en Fase 0 — especialmente lista blanca de clientes, ciclos de cobranza, frecuencia de export de BIND.
9. **Dos stakeholders disponibles** (contacto técnico + gerente administrativo) con capacidad de resolver bloqueadores en <48 hrs.
10. **Producción es el único ambiente disponible** (sin sandbox de BIND ni BUK). La plataforma operará con modo *dry-run* cuando exista riesgo de afectar BIND, con confirmación explícita del usuario antes de cualquier escritura.
11. **Acuerdo de uso de Claude API para parsing de PDFs bancarios** firmado (compromiso de no entrenamiento sobre datos del cliente, ya estándar en Anthropic API).
---
## 7. Lo que entrego más allá de código
- **Código en repositorio del cliente** (GitHub) desde el día 1 — Balam es dueño del IP
- **Documentación técnica viva** en `/docs` del repo
- **Pruebas automatizadas** para flujos críticos (facturación, conciliación)
- **Pipeline CI/CD** funcionando, despliegues con un clic
- **Backups automatizados** y plan de recuperación documentado
- **Sesión de transferencia de conocimiento** grabada
- **Soporte post-lanzamiento** de 2 semanas incluido (correcciones de bugs, no nuevo alcance)
---
## 8. Lo que no incluye esta propuesta
- Diseño visual / branding (puedo usar shadcn/ui + diseño funcional; si quieren branding pleno, sumar diseñador)
- Compra de licencias de software de terceros (PAC, agregadores, hosting)
- Cambios de proceso interno o capacitación de cambio organizacional más allá de la sesión técnica
- Integraciones no listadas (Jira, CRM, etc.) — cotizables por separado
- Garantía contra cambios fiscales del SAT que requieran rework mayor (cotizables como mantenimiento)
---
## 9. Garantía y retención
- **Bugs en funcionalidad entregada:** cobertura sin costo por 30 días después de entrega de cada fase.
- **Cambios de alcance:** se documentan como Change Request, se estiman, y se aprueban antes de ejecutar.
- **IP y código:** propiedad de Balam desde el primer commit. Retengo derecho de mencionar el proyecto en mi portafolio sin revelar información confidencial.
- **Calidad del código generado con asistencia de IA:** cualquier defecto cae bajo la misma garantía. La responsabilidad final del código es mía, sin importar la herramienta usada para escribirlo.
---
## 10. Próximos pasos
1. Reunión de kickoff (1 h) para revisar esta propuesta y resolver dudas
2. Firma de acuerdo (contrato de prestación de servicios + NDA si aplica)
3. Anticipo de 30 horas de Fase 0 para arrancar Discovery
4. Inicio inmediato — entrega del MVP en 6 semanas
---
**Contacto**
[Tu nombre]
[Tu email] · [Tu WhatsApp]
Monterrey, NL
---
## Anexo A — Trazabilidad PRD ↔ MVP (6 semanas)
Este anexo mapea cada funcionalidad y requerimiento del **PRD oficial de Balam** contra el alcance comprometido en las 6 semanas del MVP. Sirve como referencia rápida para alinear expectativas y para discutir explícitamente lo que entra, lo que entra parcial y lo que se difiere a Fase 2.
Leyenda: ✅ Cubierto · ⚠️ Parcial · ❌ Diferido a Fase 2
### A.1 Funcionalidades del MVP (PRD §3.1)
#### Facturación
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Generación automatizada de facturas | ❌ | La emisión sigue en BIND con su PAC. La generación desde eventos de negocio (Jira→BUK→Factura) depende de APIs que esos sistemas no exponen hoy — Fase 2. |
| Integración multimoneda para clientes internacionales (USD sin IVA) | ⚠️ | MXN y USD con TC del DOF y snapshot al momento de la factura (Fase 1). **EUR queda fuera del MVP**. |
| Descarga automática de facturas / reporte | ✅ | Listado con filtros, búsqueda y export Excel (Fase 1 + Fase 4). |
#### Cobranza
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Registro automático de pagos | ⚠️ | Automático **vía webhook de Stripe** para pagos con link. Pagos a cuenta bancaria se concilian desde el PDF en Fase 3, no en vivo. |
| Identificación de pagos por cliente | ✅ | Asociación a facturas, soporte de pagos parciales y completos (Fase 2). |
| Seguimiento de CxC con template de correo + lista blanca ACUNTIA y Top 3 (gestión humana) | ✅ | Lista blanca **dura** configurable, editor de template con variables, log de comunicaciones, recordatorio configurable (default 5 días antes de vencer) (Fase 2). |
#### Conciliación bancaria
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Conciliación automática pagos vs. facturas | ✅ | Match exacto, match por alias de cliente, cola de revisión humana para no-match (Fase 3). |
| Integración con estados de cuenta (3 bancos, incluye IBC Texas) | ⚠️ | **PDF upload manual + extracción estructurada vía Claude API** con validación contra totales del PDF. Integración bancaria en vivo (Belvo/Plaid/OCR portal IBC) → Fase 2. |
| Identificación de discrepancias | ✅ | Dedupe por hash, detección básica de duplicados y traspasos internos entre cuentas propias (Fase 3). |
#### Contabilidad
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Generación automática de asientos contables (ERP BIND) | ❌ | Depende de que BIND habilite API. En MVP se entrega **export en formato BIND** que el contador sube manualmente — Fase 2 para conector en vivo. |
| Integración básica con sistema contable existente | ⚠️ | Vía export/import programado, no API en vivo. |
#### Reportes y alertas
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Dashboard de estado financiero | ✅ | CxC totales y por cliente, vencidas, próximas a vencer, exportable (Fase 2). |
| Alerta — pagos pendientes | ✅ | Fase 2 |
| Alerta — errores en conciliación | ✅ | Fase 3 |
| Alerta — facturas no cobradas | ✅ | Fase 2 |
### A.2 Requerimientos funcionales (PRD §6)
| ID | Descripción | Cobertura | Notas |
|---|---|---|---|
| RF-01 | Generar facturas automáticamente desde eventos de negocio | ❌ | Fase 2 — requiere Jira/BUK como triggers |
| RF-02 | Soporte multimoneda USD/EUR/MXN | ⚠️ | MXN + USD en MVP, EUR en Fase 2 |
| RF-03 | Integración con sistemas existentes (BUK / nómina) | ❌ | BUK no expone API; Fase 2 |
| RF-04 | Registrar pagos automáticamente desde fuentes bancarias | ⚠️ | Automático vía Stripe; bancos vía PDF + conciliación |
| RF-05 | Asociar pagos a facturas | ✅ | Fase 2 |
| RF-06 | Identificar pagos parciales y completos | ✅ | Fase 2 |
| RF-07 | Conciliar automáticamente transacciones bancarias | ✅ | Fase 3 |
| RF-08 | Detectar discrepancias | ✅ | Fase 3 |
| RF-09 | Generar reportes de conciliación | ✅ | Fase 3 + 4 |
| RF-10 | Generar asientos contables automáticos | ❌ | Fase 2 (depende API BIND) |
| RF-11 | Integración con sistema contable | ⚠️ | Vía export en MVP |
| RF-12 | Dashboard financiero en tiempo real | ✅ | Fase 2 |
| RF-13 | Alertas automáticas configurables | ✅ | Fases 2-3 |
| RF-14 | Reportes exportables | ✅ | Fase 4 |
### A.3 Requerimientos no funcionales (PRD §7)
| ID | Descripción | Cobertura | Notas |
|---|---|---|---|
| RNF-01 | Arquitectura modular y escalable | ✅ | Multi-tenancy en schema (`tenant_id` + RLS) desde día 1 |
| RNF-02 | Alta disponibilidad | ⚠️ | MVP single-region en Azure; HA real (multi-region, failover) → Fase 2 |
| RNF-03 | Seguridad de datos financieros | ✅ | Secretos cifrados, RBAC, audit log universal, hardening final en Fase 4 |
| RNF-04 | Cumplimiento fiscal (México y Texas) | ⚠️ | Tax estándar en MVP; compliance avanzado Texas → Fase 2 |
| RNF-05 | Integración con APIs externas | ✅ | Stripe, Claude API, DOF para TC |
| RNF-06 | Trazabilidad completa de operaciones | ✅ | Audit log universal desde Fase 1 |
### A.4 Adicionales fuera del PRD oficial, **incluidos** en el MVP
Estas funcionalidades surgieron durante la llamada con el equipo directivo y se sumaron al alcance:
| Funcionalidad | Justificación | Fase |
|---|---|---|
| Pago con link Stripe (Checkout, tarjeta + SPEI) | Canal moderno de cobro sin depender del banco del cliente; webhook marca factura pagada automáticamente | Fase 2 |
| Parsing de PDFs bancarios con Claude API | Decisión técnica frente a parser hardcodeado por banco: los formatos cambian con frecuencia; salida estructurada robusta + validación contra totales | Fase 3 |
### A.5 Diferidos a Fase 2 — resumen ejecutivo
Lo que **no entra en las 6 semanas**, agrupado por causa raíz:
**Bloqueado por terceros (APIs no disponibles hoy):**
- Generación automática de facturas desde eventos (Jira → BUK → Factura)
- Asientos contables automáticos contra BIND
- Integración bancaria en vivo (Belvo MX / Plaid / OCR IBC)
- Integración con BUK (nómina, contratos, freelancers)
- Integración con Jira (horas trabajadas → input facturación)
**Decisión de alcance (priorizado fuera del MVP):**
- EUR (no hay clientes europeos activos hoy)
- Cash-flow proyectado 30/60/90
- Detección avanzada de anomalías (z-score, vendor nuevo, horarios atípicos)
- Domiciliación / pagos recurrentes vía Stripe Subscriptions
- Portal de cliente self-service
- App móvil para tickets físicos
- Multi-tenancy comercial con onboarding self-service
- Compliance avanzado Texas (más allá de tax estándar)
### A.6 Criterios de éxito del PRD (§13) y cómo se miden en MVP
| Criterio PRD | Cubierto | Cómo se mide en el MVP |
|---|---|---|
| Reducción de errores manuales ≥ 70% | ✅ | Línea base levantada en Fase 0; comparación al cierre del MVP sobre conciliación + cobranza |
| Disminución del tiempo de conciliación ≥ 60% | ✅ | Tiempo de conciliación pre-MVP (manual) vs. post-MVP (PDF + Claude API + cola humana) |
| Visibilidad en tiempo real | ✅ | Dashboard v1 entregado en Fase 2 |
| MVP funcional en ≤ 6 semanas | ✅ | Alcance comprometido y soft cap por fase
+85
View File
@@ -0,0 +1,85 @@
# Proyecto Balam — Plataforma de Automatización Financiera
> **Fuente de la verdad del proyecto.** Este repositorio concentra todo: propuesta, comunicaciones, fuentes y prototipos. Si pasa algo (llamada, correo, mensaje, decisión), se registra en la [bitácora](bitacora/). Empieza por aquí.
**Estado:** 🟢 **En arranque** — contrato firmado (26-jun), **kickoff con Noé el 1-jul (7am)**, plan de actividades (4 etapas) entregado. Falta que Balam entregue los **accesos** para iniciar el Discovery (sem del 6-jul).
_Última actualización: 2026-06-30._
---
## 1. Qué es
Plataforma web para centralizar y automatizar **facturación y cobranza** de Balam sobre **BIND ERP**. MVP enfocado en facturación (consulta + **emisión asistida** MXN/USD), cobranza operativa, dashboard, reportes y trazabilidad. Conciliación bancaria, pagos en línea, BUK e IA quedan para fases posteriores.
## 2. Estructura del repo
| Carpeta / archivo | Contenido |
|---|---|
| [`README.md`](README.md) | **Este archivo** — estado y datos clave del proyecto. |
| [`bitacora/`](bitacora/) | **Registro vivo**: [REGISTRO.md](bitacora/REGISTRO.md) (cronología de comunicaciones), [PENDIENTES.md](bitacora/PENDIENTES.md) (acciones abiertas), [README.md](bitacora/README.md) (cómo registrar + plantillas). |
| [`propuesta/`](propuesta/) | [Propuesta (MD)](propuesta/00%20-%20PROPUESTA-COMERCIAL.md) — contenido fuente v1.1. [PDF enviado](propuesta/Propuesta-Balam.pdf). [PDF regenerado con el skill](propuesta/Propuesta-Balam-v1.1.pdf) + su fuente reproducible ([HTML](propuesta/Propuesta-Balam.html) + [config](propuesta/pdf.config.json), build con el skill `proposal-pdf`). [Prototipo](propuesta/prototipo-mvp-fase1.html), [diagrama Fase 1 vs PRD](propuesta/diagrama-fase1-vs-prd.html). |
| [`fuentes/`](fuentes/) | Material crudo: PRD, transcripciones de llamadas, `.eml`. Evidencia, no se edita. |
| [`bind-api-sandbox/`](bind-api-sandbox/) | Prototipo técnico del cliente/mock de la API de BIND (TypeScript; el productivo será .NET). |
| [`_archivado/`](_archivado/) | Material superado (propuestas viejas, diagramas previos). Histórico. |
| [`.claude/skills/proposal-pdf/`](.claude/skills/proposal-pdf/) | **Skill** que genera el PDF de la propuesta/cotización con diseño editorial (portada full-bleed, TOC con páginas reales, footers "Confidential"). Pipeline Chromium + 2 pasos. Setup y uso en su [SKILL.md](.claude/skills/proposal-pdf/SKILL.md) (incluye nota de Windows). |
| [`planeacion/`](planeacion/) | **Plan de ejecución:** [Plan-actividades.xlsx](planeacion/Plan-actividades.xlsx) (+ [`.md`](planeacion/Plan-actividades.md)) — actividades por etapa (03) con fechas, responsables y sesiones, para el seguimiento con Erika (Jira/Gantt). |
## 3. Datos clave
**Cliente:** Balam (gestión de talento · headhunting/staff augmentation). ~45 colaboradores + 5 freelancers, ~50 facturas/mes. Jurisdicciones MX + Texas.
**Stakeholders y contactos**
| Rol | Persona | Función en el proyecto |
|---|---|---|
| CTO | Noe Rocha | Decisión técnica, arquitectura, comercial |
| CEO / Operaciones | Araceli "Ara" Sánchez | Reglas de negocio, validación directiva; cuenta maestra de BIND |
| Project Manager | Erika Chávez | **Contacto principal**, valida entregables |
| Desarrollador / técnico | Pedro Ayala | **Contacto técnico día a día**, BIND, Jira, tablero Power BI |
| Negocio / bancos | Arturo | Reglas de negocio + bancos (conciliación) |
| Recursos Humanos | Paola | Coordinación administrativa; firma del contrato (WhatsApp) |
| Proveedor | Johann Velazquez | Diseño, desarrollo, integración, entrega |
**Comunicación:** canal de WhatsApp (ágil) + correo para evidencia formal. Gestión de avances en Jira.
## 4. Alcance y comercial (propuesta v1.1)
- **MVP Fase 1:** integración BIND vía API · **emisión de facturas MXN (con IVA) / USD (sin IVA)** con dry-run + confirmación humana (timbra el PAC de BIND) · catálogo de clientes + lista blanca configurable (ACUNTIA + Top 3) · cobranza operativa (aging + alertas internas) · dashboard · reportes CSV/XLSX · bitácora · multimoneda con TC DOF.
- **Inversión:** **$67,200 $81,600 MXN + IVA** · **67 semanas** (~20 h/sem) · tarifa **$600 MXN/h** · modelo Time & Materials con tope por etapa (monto final se confirma en Discovery).
- **Sin anticipo** (política Balam): se factura la **Etapa 0 (30 h, $18,000 + IVA)** al inicio. Arranque condicionado a **contrato firmado**.
- **Pagos:** a **30 días** post-factura. Por contrato, **facturación semanal (viernes)** por horas efectivamente trabajadas (la Etapa 0 puede facturarse al inicio).
- **Garantía:** **45 días** por etapa. **Soporte:** bolsa de horas a $600/h, vigencia 12 meses (sin caducidad mensual).
- **Stack:** C#/.NET 10 + Entity Framework Core + Angular 21 + PostgreSQL 17, sobre **Azure**.
- **Diferido (Anexo B):** conciliación bancaria, pagos en línea (Stripe), recordatorios a clientes, asientos contables, BUK, IA/agentes. La automatización **por reglas** sí entra; la **IA/LLM** no.
## 5. Decisiones clave
| Fecha | Decisión |
|---|---|
| 2026-05-25 | MVP acotado a **facturación, solo BIND ERP** primero (indicación de Noe). BIND tiene API (20K req/día). |
| 2026-05-29 | Se incluye **emisión asistida** de facturas (no solo lectura), con candados fiscales. |
| 2026-06-04 | Pagos de avances a 30 días. Gestión en Jira. _(El "anticipo aprobado" en la llamada fue revertido el 8-jun: Balam no maneja anticipos.)_ |
| 2026-06-04 | Noe pide **evaluar conciliación dentro del MVP**, ajustar dashboard (vs Power BI) y renegociar vigencia de soporte → propuesta **v1.1**. |
| 2026-06-08 | Términos acordados: **sin anticipo**, pago **30 días**, garantía **45 días**, soporte como **bolsa de horas (vigencia 12 meses)**. Arranque tras **contrato firmado**. Reflejado en propuesta **v1.1**. |
| 2026-06-10 | **Propuesta v1.1 enviada.** Johann acepta los 4 ajustes (inversión supeditada a Discovery, soporte bolsa 12 meses, garantía 45 días, sin anticipo/pago 30 días) y condiciona el arranque a **contrato firmado**. Responde las 3 preguntas técnicas. |
| 2026-06-16 | **Balam acepta la v1.1.** Redacta el documento para firmar; pide a Johann esperar. Pedro armará el tablero de seguimiento en Jira con Erika. |
| 2026-06-25 | **Balam envía el contrato** de servicios para firma (vía Paola, RH). Recoge los términos v1.1 + **facturación semanal (viernes)**. |
| 2026-06-26 | **Contrato FIRMADO** por Johann. Se agregan 2 ajustes finales: **pago de horas al terminar** y **aceptación a 10 días naturales** (correcciones sobre alcance). |
| 2026-06-29 | **Arranque de ejecución:** Erika pide el **plan de actividades con fechas** (Etapa 0 y 1). Bloqueador: que Balam entregue los **accesos**. |
| 2026-06-30 | **Plan de actividades (4 etapas) entregado.** Kickoff con Noé agendado (1-jul, 7am). Erika = intermediaria de sesiones; tablero Kanban en Jira. |
## 6. Próximos pasos
Ver detalle y responsables en [bitacora/PENDIENTES.md](bitacora/PENDIENTES.md). En corto:
1. **Kickoff con Noé — mié 1-jul, 7am** (arranque del proyecto).
2. **Balam:** entregar los **accesos** (API BIND vía ARA, Azure con Guajardo/Erika, manual de marca de Pedro) y reglas/bancos con Arturo. **Bloqueador del arranque.**
3. **Johann:** cambiar régimen fiscal y confirmar permisos de Azure.
4. **Johann:** arrancar el **Discovery** (Etapa 0) la semana del 6-jul, en cuanto lleguen los accesos.
5. **Erika:** coordina sesiones (Discovery, reglas con Arturo) y el tablero Kanban en Jira.
## 7. Riesgos / puntos abiertos
- **Sin sandbox de BIND** (solo producción) → estrategia lectura primero + escritura con dry-run/confirmación/feature flag; validar comportamiento de escritura en Discovery.
- **Conciliación**: depende de procesar PDFs bancarios (sin API directa) + involucrar a Arturo.
- **Solapamiento dashboard** con el Power BI existente de Pedro.
- **Límite de API BIND** (1 llave por usuario, 20K req/día) → documentar llaves y monitorear consumo.
@@ -0,0 +1,82 @@
MENSAJES LISTOS PARA ENVIAR · BALAM
"
"1) WhatsApp para Pedro
"
"Hola Pedro, ¿cómo estás?
"
"Sí, con la información que nos compartieron y la confirmación de que BUK cuenta con API, ya puedo preparar una propuesta inicial.
"
"Mi recomendación es plantearla por fases: una primera etapa enfocada en BIND ERP para facturación, seguimiento de cobranza, alertas, reportes y trazabilidad; y dejar bancos, conciliación avanzada, BUK e IA/anomalías como fases posteriores para no inflar el MVP ni depender de todas las integraciones desde el día uno.
"
"Les compartiré la propuesta con alcance, fases, entregables, supuestos técnicos, dependencias y timeline estimado. También incluiré un checklist de accesos e información para que podamos iniciar rápido una vez aprobada.
"
"Gracias por el seguimiento. Quedo atento.
"
"---
"
"2) Correo de respuesta a la actualización de BUK
"
"Asunto: Re: Actualización API BUK / Propuesta Balam
"
"Buenos días, Pedro / equipo Balam,
"
"Muchas gracias por la actualización. La confirmación de que BUK cuenta con soporte API nos ayuda a dejar preparada la arquitectura para una integración posterior, aunque entiendo que no es la prioridad inmediata.
"
"Con la información del PRD, la llamada con Noe y la actualización de BIND/BUK, ya puedo estructurar una propuesta inicial realista. La plantearé en fases, iniciando con un MVP enfocado en BIND ERP para facturación, seguimiento de cobranza, alertas, reportes y trazabilidad.
"
"La intención es que la primera etapa les entregue valor operativo sin depender desde el inicio de bancos, BUK, Book, Jira o automatizaciones de IA más avanzadas. Esas integraciones quedarían consideradas dentro del roadmap y se cotizarían/validarían conforme avancemos en discovery técnico.
"
"Les comparto la propuesta para revisión. Quedo atento a comentarios y a la disponibilidad para una sesión corta de revisión técnica/comercial.
"
"Saludos,
Johann Velazquez
"
"---
"
"3) Correo para enviar la propuesta
"
"Asunto: Propuesta inicial · Plataforma de automatización financiera Balam
"
"Hola Noe, Erika, Ara y Pedro,
"
"Les comparto la propuesta inicial para el desarrollo de la plataforma de automatización financiera de Balam.
"
"Tomando como base el PRD, la llamada de seguimiento y los últimos comentarios sobre BIND y BUK, propongo iniciar con un MVP BIND-first enfocado en facturación, seguimiento de cobranza, alertas, reportes y trazabilidad. Este enfoque permite avanzar de forma realista y reducir riesgo técnico, dejando preparada la arquitectura para integrar bancos, BUK, conciliación bancaria, contabilidad e IA/agentes en fases posteriores.
"
"El documento incluye:
- alcance funcional y técnico del MVP;
- fases sugeridas;
- entregables;
- supuestos y dependencias;
- criterios de aceptación;
- elementos fuera de alcance de la primera fase;
- checklist de información y accesos necesarios.
"
"Quedo atento a sus comentarios. Si lo consideran conveniente, podemos agendar una sesión corta para revisar el alcance y ajustar la propuesta antes de pasar a aprobación.
"
"Saludos,
Johann Velazquez
@@ -0,0 +1,11 @@
Paquete de documentos Balam - MVP BIND-first
Contenido:
00_Resumen_Ejecutivo_Requerimiento_Balam.docx - Lectura ejecutiva y narrativa de alcance.
01_Propuesta_Tecnica_Comercial_Balam_MVP_BIND_First.docx - Documento principal para enviar al cliente.
02_SOW_Alcance_MVP_BIND_First_Balam.docx - Alcance formal/SOW para controlar expectativas.
03_Anexo_Tecnico_Integraciones_Discovery_Balam.docx - Detalle técnico para Noe/Pedro.
04_Checklist_Accesos_Datos_Dependencias_Balam.docx - Checklist operativo para iniciar discovery.
05_Mensajes_Listos_Para_Enviar_Balam.docx/.txt - Copys de WhatsApp y correos.
Nota: Completar montos comerciales antes de enviar la propuesta final.
File diff suppressed because it is too large Load Diff
+740
View File
@@ -0,0 +1,740 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Balam · Plan de construcción por fases</title>
<style>
:root{
--bg:#ffffff;
--ink:#0f172a;
--muted:#64748b;
--line:#e2e8f0;
--soft:#f8fafc;
--navy:#1e293b;
--peach:#fed7aa;
--peach-ink:#9a3412;
--lavender:#ddd6fe;
--lavender-ink:#5b21b6;
--amber:#fde68a;
--amber-ink:#92400e;
--mint:#bbf7d0;
--mint-ink:#14532d;
--rose:#fecaca;
--rose-ink:#991b1b;
--sky:#bae6fd;
--sky-ink:#075985;
--slate:#e2e8f0;
--slate-ink:#334155;
}
*{box-sizing:border-box}
html,body{background:var(--bg)}
body{
margin:0 auto; padding:48px 56px; max-width:1280px;
font-family:-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif;
color:var(--ink);
}
h1{font-size:38px; font-weight:800; margin:0 0 6px; letter-spacing:-0.02em}
.lede{color:var(--muted); font-size:16px; margin:0 0 28px; max-width:820px; line-height:1.5}
.section-label{
font-size:11px; font-weight:600; color:var(--muted);
letter-spacing:2px; text-transform:uppercase;
margin:0 0 10px;
}
.block-title{
font-size:22px; font-weight:700; margin:0 0 4px;
display:flex; align-items:center; gap:12px; letter-spacing:-0.01em;
}
.block-sub{color:var(--muted); font-size:14px; margin:0 0 26px; max-width:760px; line-height:1.5}
/* ---- color utilities (lectura / escritura / no-toca / futuro) ---- */
.t-read {background:#ecfdf5; border-color:#bbf7d0; color:#14532d}
.t-write {background:#fff7ed; border-color:#fed7aa; color:#9a3412}
.t-none {background:#f8fafc; border-color:#e2e8f0; color:#64748b}
.t-future{background:#f5f3ff; border-color:#ddd6fe; color:#5b21b6}
/* ---- leyenda ---- */
.legend{display:flex; gap:10px; flex-wrap:wrap; margin:0 0 44px}
.legend .item{
display:inline-flex; align-items:center; gap:8px; font-size:13px;
color:var(--slate-ink); border:1px solid var(--line); background:var(--soft);
padding:8px 14px; border-radius:10px; font-weight:600;
}
.legend .ic{font-size:15px}
/* ---- tile base (reutilizado) ---- */
.tile{
width:88px; height:88px; border-radius:20px;
display:flex; align-items:center; justify-content:center;
font-size:38px; flex:0 0 auto;
box-shadow:0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.04);
}
.tile.navy {background:var(--navy); color:#fff}
.tile.peach {background:var(--peach); color:var(--peach-ink)}
.tile.lavender{background:var(--lavender); color:var(--lavender-ink)}
.tile.amber {background:var(--amber); color:var(--amber-ink)}
.tile.mint {background:var(--mint); color:var(--mint-ink)}
.tile.rose {background:var(--rose); color:var(--rose-ink)}
.tile.sky {background:var(--sky); color:var(--sky-ink)}
.tile.slate {background:var(--slate); color:var(--slate-ink)}
/* ================= LÍNEA DE TIEMPO VERTICAL ================= */
.timeline{margin:0 0 56px}
.phase{
position:relative;
display:grid; grid-template-columns:88px 1fr; gap:28px;
padding-bottom:28px;
}
.phase::before{ /* riel vertical de la línea de tiempo */
content:""; position:absolute; left:43px; top:100px; bottom:-6px;
width:2px; background:var(--line); z-index:0;
}
.phase:last-child::before{display:none}
.phase.lead-roadmap::before{ /* tramo punteado hacia el roadmap */
background:transparent; width:0; border-left:2px dashed #cbd5e1; left:42px;
}
.phase .tile{position:relative; z-index:1}
.phase-card{
border:1px solid var(--line); border-radius:18px;
padding:20px 24px 22px;
box-shadow:0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.04);
}
.ph-kicker{
font-size:11px; font-weight:700; letter-spacing:1.6px; text-transform:uppercase;
color:var(--muted); margin:0 0 3px;
}
.ph-title{font-size:19px; font-weight:700; margin:0 0 12px; letter-spacing:-0.01em}
.ph-card-list{margin:0; padding-left:18px; font-size:13.5px; color:var(--slate-ink); line-height:1.55}
.ph-card-list li{margin:4px 0}
.ph-card-list b{color:var(--ink); font-weight:600}
.ph-status{display:flex; flex-wrap:wrap; gap:10px; margin-top:16px}
.status{
display:inline-flex; align-items:center; gap:8px;
padding:8px 13px; border-radius:11px; font-size:12.5px;
border:1px solid var(--line); line-height:1.3;
}
.status .ic{font-size:14px; flex:0 0 auto}
.status b{font-weight:700}
/* ---- variante Roadmap (post-MVP) ---- */
.phase.roadmap .phase-card{
border:1.5px dashed #cbd5e1; background:#fcfcfd; box-shadow:none;
}
.phase.roadmap .tile{opacity:.9}
.rm-badge{
display:inline-flex; align-items:center; gap:7px;
background:#f5f3ff; border:1px solid #ddd6fe; color:#5b21b6;
font-size:11px; font-weight:700; letter-spacing:.4px;
padding:5px 11px; border-radius:999px; margin:0 0 12px;
}
/* ================= SWIMLANE INTEGRACIÓN EN EL TIEMPO ================= */
.swim-wrap{overflow-x:auto; margin:0 0 12px; padding-bottom:6px}
.swim{
display:grid;
grid-template-columns:118px repeat(6, minmax(140px,1fr));
gap:10px; min-width:920px;
}
.swim .corner{}
.swim .ch{
text-align:center; font-size:13px; font-weight:700; color:var(--ink);
padding:4px 4px 2px; align-self:end;
}
.swim .ch small{
display:block; color:var(--muted); font-weight:600; font-size:10px;
text-transform:uppercase; letter-spacing:1px; margin-top:2px;
}
.swim .ch.rm{color:var(--lavender-ink)}
.swim .lane{
display:flex; align-items:center; gap:9px;
font-weight:700; font-size:14px; color:var(--ink);
}
.swim .lane .dot{
width:26px; height:26px; border-radius:8px; flex:0 0 auto;
display:flex; align-items:center; justify-content:center; font-size:15px;
}
.swim .cell{
border:1px solid var(--line); border-radius:11px;
padding:11px 13px; font-size:12.5px; font-weight:700;
display:flex; flex-direction:column; justify-content:center; min-height:58px;
}
.swim .cell small{display:block; font-weight:600; opacity:.78; font-size:10.5px; margin-top:3px}
.swim .cell.rm{border-style:dashed}
/* ================= DIAGRAMA ESTADO FINAL ================= */
.group{
border:1.5px dashed #cbd5e1; border-radius:18px;
padding:30px 20px 22px; position:relative; margin-top:6px;
}
.group::before{
content:attr(data-label);
position:absolute; top:-9px; left:18px;
background:#fff; padding:0 8px;
font-size:10px; font-weight:700; letter-spacing:1.5px; text-transform:uppercase;
color:var(--lavender-ink);
}
.flow{
display:flex; align-items:flex-start; gap:6px;
overflow-x:auto; padding:4px 4px 8px;
}
.node{flex:0 0 auto; width:120px; text-align:center}
.node .tile{margin:0 auto 12px}
.node .label{font-size:13px; font-weight:700; line-height:1.25}
.node .sub{font-size:11px; color:var(--muted); margin-top:3px; line-height:1.3}
.arrow{
flex:0 0 auto; display:flex; flex-direction:column; align-items:center; gap:5px;
padding-top:30px; user-select:none;
}
.arrow .gly{color:#cbd5e1; font-size:22px; line-height:1}
.arrow .cap{font-size:9.5px; color:var(--muted); max-width:78px; text-align:center; line-height:1.25; font-weight:600}
/* ================= TARJETAS NARRATIVAS (dolor / visión / enfoque) ================= */
.divider{height:1px; background:var(--line); margin:48px 0}
.cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(232px,1fr)); gap:16px}
.info-card{
border:1px solid var(--line); border-radius:16px; padding:18px 20px; background:#fff;
box-shadow:0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.04);
}
.info-card .ic-head{display:flex; align-items:center; gap:11px; margin-bottom:9px}
.info-card .ic-emoji{
width:40px; height:40px; border-radius:12px; flex:0 0 auto;
display:flex; align-items:center; justify-content:center; font-size:20px;
}
.info-card h4{margin:0; font-size:14px; font-weight:700; line-height:1.25}
.info-card p{margin:0; font-size:12.5px; color:var(--slate-ink); line-height:1.5}
/* ---- flow compacto (cadena manual del dolor) ---- */
.flow.compact{gap:2px}
.flow.compact .node{width:104px}
.flow.compact .node .tile{width:72px; height:72px; font-size:30px; border-radius:16px}
.flow.compact .arrow{padding-top:22px}
/* ================= ARQUITECTURA ================= */
.arch{margin-top:4px}
.band-label{
font-size:10px; font-weight:700; letter-spacing:1.4px; text-transform:uppercase;
color:var(--muted); margin:0 0 11px;
}
.arch-row{display:flex; gap:14px; flex-wrap:wrap}
.arch-row + .band-label{margin-top:20px}
.arch-card{
flex:1 1 210px; min-width:200px;
border:1px solid var(--line); border-radius:14px; padding:14px 16px; background:#fff;
display:flex; align-items:center; gap:13px;
box-shadow:0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.04);
}
.arch-card .ico{
width:46px; height:46px; border-radius:12px; flex:0 0 auto;
display:flex; align-items:center; justify-content:center; font-size:23px;
box-shadow:0 1px 2px rgba(15,23,42,.06);
}
.ico.navy {background:var(--navy); color:#fff}
.ico.peach {background:var(--peach); color:var(--peach-ink)}
.ico.lavender{background:var(--lavender); color:var(--lavender-ink)}
.ico.amber {background:var(--amber); color:var(--amber-ink)}
.ico.mint {background:var(--mint); color:var(--mint-ink)}
.ico.sky {background:var(--sky); color:var(--sky-ink)}
.ico.slate {background:var(--slate); color:var(--slate-ink)}
.arch-card .ac-t{font-weight:700; font-size:13.5px; line-height:1.2}
.arch-card .ac-s{font-size:11px; color:var(--muted); margin-top:3px; line-height:1.3}
.arch-card.rm{border-style:dashed; background:#fcfcfd}
.arch-card.rm .ico{opacity:.9}
.arch-down{text-align:center; color:#cbd5e1; font-size:22px; margin:8px 0; user-select:none; line-height:1.1}
.arch-down small{display:block; font-size:10px; color:var(--muted); letter-spacing:1px; text-transform:uppercase; font-weight:700}
.arch .group{padding:26px 20px 20px; margin-top:0}
.arch .group::before{color:var(--slate-ink)}
.mini-pills{display:flex; gap:5px; margin-top:6px; flex-wrap:wrap}
.mini-pill{font-size:9.5px; font-weight:700; padding:2px 8px; border-radius:999px; border:1px solid; white-space:nowrap}
/* ================= RESPONSIVE ================= */
@media (max-width:1100px){
body{padding:36px 30px}
h1{font-size:32px}
.swim{grid-template-columns:108px repeat(6, minmax(132px,1fr))}
}
@media (max-width:700px){
body{padding:26px 18px}
h1{font-size:26px}
.lede{font-size:14px}
.block-title{font-size:19px}
.phase{grid-template-columns:56px 1fr; gap:16px}
.phase .tile{width:56px; height:56px; font-size:25px; border-radius:16px}
.phase::before{left:27px; top:66px}
.phase.lead-roadmap::before{left:26px}
.phase-card{padding:16px 17px 18px}
.ph-title{font-size:17px}
.node{width:108px}
.arch-card{flex-basis:100%; min-width:0}
}
</style>
</head>
<body>
<h1>Balam · Del dolor actual a la plataforma orquestada</h1>
<p class="lede">La historia completa de un vistazo: <b>por qué duele hoy</b>, <b>a qué queremos llegar</b>, <b>cómo lo solucionamos</b>, con <b>qué arquitectura</b> y <b>en qué orden</b> lo construimos. El MVP son las Fases&nbsp;04 (6 semanas, medio tiempo); la fase Post-MVP es roadmap futuro, no incluido en la cotización.</p>
<!-- ====== LEYENDA ====== -->
<div class="legend">
<span class="item"><span class="ic"></span> Lectura (consumir datos)</span>
<span class="item"><span class="ic">✍️</span> Escritura (escribir vía API)</span>
<span class="item"><span class="ic">⏸️</span> No se toca / fuera de scope</span>
<span class="item"><span class="ic">🔮</span> Visión futura (roadmap)</span>
</div>
<!-- ====================================================== -->
<!-- ====== EL DOLOR (HOY) ====== -->
<!-- ====================================================== -->
<p class="section-label">El dolor · hoy</p>
<h2 class="block-title">Todo el ciclo financiero se mueve a mano</h2>
<p class="block-sub">Ocho pasos encadenados, todos dependientes de una persona moviendo datos entre sistemas que no se hablan. Cada eslabón es una oportunidad de error y de retraso.</p>
<div class="flow compact">
<div class="node"><div class="tile slate">🕐</div><div class="label">Jira</div><div class="sub">Horas por cliente</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile slate">👥</div><div class="label">BUK</div><div class="sub">Nómina aprobada</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">✍️</div><div class="label">Captura factura</div><div class="sub">Manual en BIND</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">📧</div><div class="label">Cobranza</div><div class="sub">Correo a mano</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">⬇️</div><div class="label">PDFs banco</div><div class="sub">Descarga manual</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">📊</div><div class="label">Excel</div><div class="sub">Conciliación</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">📒</div><div class="label">Contador</div><div class="sub">Captura asientos</div></div>
<div class="arrow"><span class="gly"></span></div>
<div class="node"><div class="tile rose">📈</div><div class="label">Reporte CxC</div><div class="sub">Llega tarde</div></div>
</div>
<div class="cards" style="margin-top:22px">
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-write">🎯</span><h4>Error humano caro</h4></div>
<p>Un recordatorio de cobranza enviado por equivocación a ACUNTIA o al Top 3 daña la relación con el cliente más importante.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-write"></span><h4>Horas a mano cada mes</h4></div>
<p>Conciliar 3 estados de cuenta bancarios en PDF, línea por línea, contra las facturas que viven en BIND.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-write">🐢</span><h4>Información que llega tarde</h4></div>
<p>Dirección no ve el estado real de la cobranza del día; el reporte llega cuando la decisión ya pasó.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-write">🧵</span><h4>Proceso frágil</h4></div>
<p>Todo depende de que una persona recuerde capturar, cobrar y descargar a tiempo. Si falta, el ciclo se detiene.</p>
</div>
</div>
<div class="divider"></div>
<!-- ====================================================== -->
<!-- ====== A QUÉ QUEREMOS LLEGAR (VISIÓN) ====== -->
<!-- ====================================================== -->
<p class="section-label">A qué queremos llegar · la visión</p>
<h2 class="block-title">Una plataforma que absorbe lo repetitivo</h2>
<p class="block-sub">Las personas dejan de mover datos y solo tocan las excepciones. El resultado: cobranza segura, conciliación automática y visibilidad en tiempo real.</p>
<div class="cards">
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read"></span><h4>Cobranza con candado</h4></div>
<p>Lista blanca dura: ACUNTIA y el Top 3 nunca reciben un recordatorio automático, por diseño.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read"></span><h4>Cobro más rápido</h4></div>
<p>Link de pago Stripe directo en el correo; el webhook marca la factura como pagada sin intervención.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read">🤖</span><h4>Conciliación automática</h4></div>
<p>El match exacto se resuelve solo; el equipo solo revisa lo que no cuadra, en una cola dedicada.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read">📊</span><h4>Visibilidad en vivo</h4></div>
<p>Dashboard de CxC y CxP en tiempo real para CEO, CFO y CTO, con multimoneda MXN + USD.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read">🧾</span><h4>Asientos sin captura</h4></div>
<p>En el roadmap, BIND recibe pagos y asientos vía API y se elimina el export a Excel.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-read">🔒</span><h4>Todo trazable</h4></div>
<p>Audit log de quién hizo qué y cuándo, con roles y permisos granulares (RBAC).</p>
</div>
</div>
<div class="divider"></div>
<!-- ====================================================== -->
<!-- ====== CÓMO LO SOLUCIONAMOS (ENFOQUE) ====== -->
<!-- ====================================================== -->
<p class="section-label">Cómo lo solucionamos · el enfoque</p>
<h2 class="block-title">Una capa de orquestación sobre BIND</h2>
<p class="block-sub">No reemplazamos nada: BIND sigue siendo la fuente de verdad y el único que emite CFDI. La plataforma coordina el flujo, automatiza lo repetitivo y usa IA para lo difícil.</p>
<div class="cards">
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-future">🧩</span><h4>Capa sobre BIND, no reemplazo</h4></div>
<p>La plataforma orquesta el flujo; BIND conserva facturación, timbrado CFDI y contabilidad.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-future">🤖</span><h4>IA para lo difícil</h4></div>
<p>Claude extrae los movimientos de cada PDF bancario (1 pipeline por banco) y valida totales.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-future">⚙️</span><h4>Automatizar lo repetitivo</h4></div>
<p>Sync de facturas, recordatorios y match corren solos con un motor de cron + worker.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-future">🙋</span><h4>Humano solo en excepciones</h4></div>
<p>Lo que no hace match cae en una cola de revisión: nada se pierde y nada se cobra a ciegas.</p>
</div>
<div class="info-card">
<div class="ic-head"><span class="ic-emoji t-future">🛡️</span><h4>Control y trazabilidad</h4></div>
<p>RBAC, audit log universal y lista blanca dura protegen el proceso de errores costosos.</p>
</div>
</div>
<div class="divider"></div>
<!-- ====================================================== -->
<!-- ====== LA ARQUITECTURA ====== -->
<!-- ====================================================== -->
<p class="section-label">La arquitectura</p>
<h2 class="block-title">Con qué se construye, por capas</h2>
<p class="block-sub">Una sola plataforma desplegada en Azure, organizada en tres capas: las personas que la usan, la aplicación que orquesta, y los sistemas externos a los que se conecta.</p>
<div class="arch">
<!-- Capa 1: Usuarios -->
<p class="band-label">① Usuarios · acceso por rol</p>
<div class="arch-row">
<div class="arch-card">
<span class="ico peach">💰</span>
<div><div class="ac-t">Finanzas</div><div class="ac-s">Cobranza, pagos y conciliación</div></div>
</div>
<div class="arch-card">
<span class="ico amber">🏢</span>
<div><div class="ac-t">Dirección</div><div class="ac-s">Dashboard CxC / CxP en vivo</div></div>
</div>
<div class="arch-card">
<span class="ico slate">🛠️</span>
<div><div class="ac-t">Operaciones</div><div class="ac-s">Carga de PDFs y revisión</div></div>
</div>
</div>
<div class="arch-down"><small>RBAC + autenticación</small></div>
<!-- Capa 2: Plataforma -->
<div class="group" data-label="② Plataforma Balam · desplegada en Azure">
<p class="band-label">Aplicación</p>
<div class="arch-row">
<div class="arch-card">
<span class="ico sky">🖥️</span>
<div><div class="ac-t">UI Web</div><div class="ac-s">Facturas, dashboard, cobranza, conciliación</div></div>
</div>
<div class="arch-card">
<span class="ico lavender">🔌</span>
<div><div class="ac-t">API core</div><div class="ac-s">Auth, roles, reglas de negocio</div></div>
</div>
<div class="arch-card">
<span class="ico lavender"></span>
<div><div class="ac-t">Worker + Cron</div><div class="ac-s">Sync, recordatorios, motor de match</div></div>
</div>
</div>
<p class="band-label">Datos &amp; almacenamiento</p>
<div class="arch-row">
<div class="arch-card">
<span class="ico mint">🗄️</span>
<div><div class="ac-t">PostgreSQL</div><div class="ac-s">Facturas, pagos, conciliación, audit log</div></div>
</div>
<div class="arch-card">
<span class="ico mint">📦</span>
<div><div class="ac-t">Blob Storage</div><div class="ac-s">PDFs bancarios y respaldos</div></div>
</div>
</div>
</div>
<div class="arch-down"><small>APIs · webhooks · archivos</small></div>
<!-- Capa 3: Integraciones -->
<div class="group" data-label="③ Servicios e integraciones externas">
<p class="band-label">Activas en el MVP</p>
<div class="arch-row">
<div class="arch-card">
<span class="ico navy">📘</span>
<div>
<div class="ac-t">BIND ERP · API</div>
<div class="ac-s">Facturas, clientes, catálogo · fuente de verdad</div>
<div class="mini-pills">
<span class="mini-pill t-read">✅ Lectura</span>
<span class="mini-pill t-write">✍️ Escritura (roadmap)</span>
</div>
</div>
</div>
<div class="arch-card">
<span class="ico peach">🤖</span>
<div><div class="ac-t">Claude API</div><div class="ac-s">Parsing de PDFs bancarios</div></div>
</div>
<div class="arch-card">
<span class="ico mint">💳</span>
<div><div class="ac-t">Stripe</div><div class="ac-s">Pago con link + webhook de pagado</div></div>
</div>
<div class="arch-card">
<span class="ico sky">💱</span>
<div><div class="ac-t">DOF</div><div class="ac-s">Tipo de cambio MXN / USD</div></div>
</div>
</div>
<p class="band-label">Roadmap futuro 🔮</p>
<div class="arch-row">
<div class="arch-card rm">
<span class="ico slate">👥</span>
<div><div class="ac-t">BUK · API</div><div class="ac-s">Nómina aprobada → factura automática</div></div>
</div>
<div class="arch-card rm">
<span class="ico slate">🕐</span>
<div><div class="ac-t">Jira</div><div class="ac-s">Horas por colaborador-cliente</div></div>
</div>
<div class="arch-card rm">
<span class="ico slate">🏦</span>
<div><div class="ac-t">Belvo / Plaid</div><div class="ac-s">Banca en vivo (elimina PDFs)</div></div>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- ====================================================== -->
<!-- ====== LÍNEA DE TIEMPO ====== -->
<!-- ====================================================== -->
<p class="section-label">El plan · cómo se construye · 6 fases</p>
<h2 class="block-title">En qué orden lo entregamos</h2>
<p class="block-sub">Cada fase entrega algo usable. BIND arranca solo como exploración, pasa a lectura activa y, ya en el roadmap, a escritura. BUK queda fuera hasta que exponga una API estable.</p>
<div class="timeline">
<!-- FASE 0 -->
<div class="phase">
<div class="tile slate">🔍</div>
<div class="phase-card">
<p class="ph-kicker">Fase 0 · Semana 1</p>
<h3 class="ph-title">Discovery</h3>
<ul class="ph-card-list">
<li>Validar <b>BIND API</b>: auth, sandbox, webhooks y endpoints de escritura.</li>
<li>Recolectar <b>3 PDFs bancarios</b> reales anonimizados (Banorte, Intercam, IBC Texas).</li>
<li>Validar parsing con <b>Claude API</b> sobre los PDFs.</li>
<li>Setup de <b>Azure + repositorio + CI/CD</b>.</li>
</ul>
<div class="ph-status">
<span class="status t-none"><span class="ic">⏸️</span><span><b>BIND</b> · Solo exploración, sin integración</span></span>
<span class="status t-none"><span class="ic">⏸️</span><span><b>BUK</b> · Fuera de scope</span></span>
</div>
</div>
</div>
<!-- FASE 1 -->
<div class="phase">
<div class="tile sky">🔄</div>
<div class="phase-card">
<p class="ph-kicker">Fase 1 · Semanas 23</p>
<h3 class="ph-title">Sync BIND + plataforma base</h3>
<ul class="ph-card-list">
<li><b>Conector BIND API (lectura)</b>: facturas, clientes y catálogo.</li>
<li>Modelo central de facturas con estados.</li>
<li>Multimoneda <b>MXN + USD</b> (tipo de cambio del DOF).</li>
<li>Auth, roles y audit log · UI: listado, filtros y detalle de facturas.</li>
</ul>
<div class="ph-status">
<span class="status t-read"><span class="ic"></span><span><b>BIND</b> · Lectura activa vía API (sync programado)</span></span>
<span class="status t-none"><span class="ic">⏸️</span><span><b>BUK</b> · Fuera de scope</span></span>
</div>
</div>
</div>
<!-- FASE 2 -->
<div class="phase">
<div class="tile lavender">📬</div>
<div class="phase-card">
<p class="ph-kicker">Fase 2 · Semana 4</p>
<h3 class="ph-title">Cobranza + Dashboard + Pago link</h3>
<ul class="ph-card-list">
<li>Registro manual de pagos asociados a facturas + editor de template de cobranza.</li>
<li>Motor de recordatorios automáticos (<b>cron + worker</b>).</li>
<li><b>Lista blanca dura</b>: ACUNTIA + Top 3 nunca reciben auto-recordatorio.</li>
<li>Pago con link <b>Stripe (Checkout)</b> + webhook que marca pagado · Dashboard CxC en vivo.</li>
</ul>
<div class="ph-status">
<span class="status t-read"><span class="ic"></span><span><b>BIND</b> · Fuente de verdad (lectura)</span></span>
<span class="status t-none"><span class="ic">⏸️</span><span><b>BUK</b> · Fuera de scope</span></span>
</div>
</div>
</div>
<!-- FASE 3 -->
<div class="phase">
<div class="tile peach">🔗</div>
<div class="phase-card">
<p class="ph-kicker">Fase 3 · Semana 5</p>
<h3 class="ph-title">Conciliación bancaria (PDF)</h3>
<ul class="ph-card-list">
<li>Upload manual de PDFs + extracción estructurada con <b>Claude API</b> (1 pipeline por banco).</li>
<li>Validación de totales (sumatoria vs. resumen del PDF).</li>
<li>Motor de conciliación: <b>match exacto auto</b> · alias semi-auto · no-match → cola humana.</li>
<li>Detección de duplicados y traspasos internos.</li>
</ul>
<div class="ph-status">
<span class="status t-read"><span class="ic"></span><span><b>BIND</b> · Lectura para hacer match contra facturas</span></span>
<span class="status t-none"><span class="ic">⏸️</span><span><b>BUK</b> · Fuera de scope</span></span>
</div>
</div>
</div>
<!-- FASE 4 -->
<div class="phase lead-roadmap">
<div class="tile mint">📈</div>
<div class="phase-card">
<p class="ph-kicker">Fase 4 · Semana 6</p>
<h3 class="ph-title">Reportes + cierre del MVP</h3>
<ul class="ph-card-list">
<li>Export <b>Excel/CSV compatible con BIND</b> (el contador sube los asientos manualmente).</li>
<li>Reportes mensuales de <b>CxC y CxP</b>.</li>
<li>Backups + hardening + <b>RBAC granular</b>.</li>
<li>Documentación + capacitación.</li>
</ul>
<div class="ph-status">
<span class="status t-read"><span class="ic"></span><span><b>BIND</b> · Lectura + export Excel hacia BIND (manual por contador)</span></span>
<span class="status t-none"><span class="ic">⏸️</span><span><b>BUK</b> · Fuera de scope</span></span>
</div>
</div>
</div>
<!-- POST-MVP -->
<div class="phase roadmap">
<div class="tile amber">🔮</div>
<div class="phase-card">
<p class="ph-kicker">Post-MVP · Roadmap futuro</p>
<span class="rm-badge">🔮 Roadmap · no incluido en el MVP / cotización</span>
<h3 class="ph-title">Cierre del ciclo end-to-end</h3>
<ul class="ph-card-list">
<li><b>BIND escritura vía API</b>: registra pagos y genera asientos contables automáticos (elimina el export Excel).</li>
<li><b>BUK</b>: cuando exponga API, la nómina aprobada dispara la factura automática en BIND vía la plataforma.</li>
<li><b>Jira</b>: horas por colaborador-cliente como input de facturación.</li>
<li><b>Belvo/Plaid</b>: integración bancaria en vivo (elimina el upload de PDFs).</li>
</ul>
<div class="ph-status">
<span class="status t-write"><span class="ic">✍️</span><span><b>BIND</b> · Escritura vía API (pagos + asientos automáticos)</span></span>
<span class="status t-future"><span class="ic">🔮</span><span><b>BUK</b> · Integración end-to-end (Jira → BUK → factura)</span></span>
</div>
</div>
</div>
</div>
<!-- ====================================================== -->
<!-- ====== SWIMLANE: INTEGRACIÓN A LO LARGO DEL TIEMPO ====== -->
<!-- ====================================================== -->
<p class="section-label">Integración a lo largo del tiempo</p>
<h2 class="block-title">Cómo evoluciona el rol de BIND y BUK</h2>
<p class="block-sub">De un vistazo: BIND pasa de exploración → lectura → escritura. BUK permanece fuera de scope durante todo el MVP y solo entra en el roadmap.</p>
<div class="swim-wrap">
<div class="swim">
<!-- fila encabezados -->
<div class="corner"></div>
<div class="ch">Fase 0<small>Sem 1</small></div>
<div class="ch">Fase 1<small>Sem 23</small></div>
<div class="ch">Fase 2<small>Sem 4</small></div>
<div class="ch">Fase 3<small>Sem 5</small></div>
<div class="ch">Fase 4<small>Sem 6</small></div>
<div class="ch rm">Post-MVP<small>Roadmap</small></div>
<!-- fila BIND -->
<div class="lane"><span class="dot" style="background:var(--navy);color:#fff">📘</span> BIND</div>
<div class="cell t-none">⏸️ Exploración<small>validar API</small></div>
<div class="cell t-read">✅ Lectura<small>sync programado</small></div>
<div class="cell t-read">✅ Lectura<small>fuente de verdad</small></div>
<div class="cell t-read">✅ Lectura<small>match facturas</small></div>
<div class="cell t-read">✅ Lectura<small>+ export Excel</small></div>
<div class="cell t-write rm">✍️ Escritura<small>pagos + asientos</small></div>
<!-- fila BUK -->
<div class="lane"><span class="dot" style="background:var(--sky);color:var(--sky-ink)">👥</span> BUK</div>
<div class="cell t-none">⏸️ Fuera de scope<small></small></div>
<div class="cell t-none">⏸️ Fuera de scope<small></small></div>
<div class="cell t-none">⏸️ Fuera de scope<small></small></div>
<div class="cell t-none">⏸️ Fuera de scope<small></small></div>
<div class="cell t-none">⏸️ Fuera de scope<small></small></div>
<div class="cell t-future rm">🔮 Integración<small>nómina → factura</small></div>
</div>
</div>
<!-- ====================================================== -->
<!-- ====== DIAGRAMA ESTADO FINAL POST-MVP ====== -->
<!-- ====================================================== -->
<p class="section-label" style="margin-top:52px">Estado final · Roadmap post-MVP</p>
<h2 class="block-title">El ciclo cerrado end-to-end</h2>
<p class="block-sub">Visión objetivo una vez completado el roadmap: cada sistema dispara al siguiente sin intervención manual. Las flechas indican quién dispara a quién.</p>
<div class="group" data-label="🔮 Estado objetivo post-MVP">
<div class="flow">
<div class="node">
<div class="tile slate">🕐</div>
<div class="label">Jira</div>
<div class="sub">Horas por colaborador-cliente</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">horas aprobadas</span></div>
<div class="node">
<div class="tile sky">👥</div>
<div class="label">BUK</div>
<div class="sub">Nómina aprobada</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">nómina aprobada</span></div>
<div class="node">
<div class="tile lavender">⚙️</div>
<div class="label">Plataforma</div>
<div class="sub">Orquesta y genera factura</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">factura vía API</span></div>
<div class="node">
<div class="tile navy">📘</div>
<div class="label">BIND ERP</div>
<div class="sub">Timbra CFDI + asiento</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">CFDI / cobro</span></div>
<div class="node">
<div class="tile mint">🏦</div>
<div class="label">Banco</div>
<div class="sub">Cobro / movimientos</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">mov. en vivo · Belvo/Plaid</span></div>
<div class="node">
<div class="tile peach">🔗</div>
<div class="label">Conciliación</div>
<div class="sub">Match automático</div>
</div>
<div class="arrow"><span class="gly"></span><span class="cap">asiento automático</span></div>
<div class="node">
<div class="tile amber">📈</div>
<div class="label">Reporte</div>
<div class="sub">CxC / CxP + asiento</div>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,372 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Balam · Flujo financiero</title>
<style>
:root{
--bg:#ffffff;
--ink:#0f172a;
--muted:#64748b;
--line:#e2e8f0;
--soft:#f8fafc;
--navy:#1e293b;
--peach:#fed7aa;
--peach-ink:#9a3412;
--lavender:#ddd6fe;
--lavender-ink:#5b21b6;
--amber:#fde68a;
--amber-ink:#92400e;
--mint:#bbf7d0;
--mint-ink:#14532d;
--rose:#fecaca;
--rose-ink:#991b1b;
--sky:#bae6fd;
--sky-ink:#075985;
--slate:#e2e8f0;
--slate-ink:#334155;
}
*{box-sizing:border-box}
html,body{background:var(--bg)}
body{
margin:0; padding:48px 56px;
font-family:-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif;
color:var(--ink);
}
h1{font-size:38px; font-weight:800; margin:0 0 6px; letter-spacing:-0.02em}
.lede{color:var(--muted); font-size:16px; margin:0 0 48px; max-width:780px}
.section-label{
font-size:11px; font-weight:600; color:var(--muted);
letter-spacing:2px; text-transform:uppercase;
margin:0 0 24px;
}
.flow-block{margin-bottom:56px}
.flow-title{
font-size:22px; font-weight:700; margin:0 0 4px;
display:flex; align-items:center; gap:12px;
}
.flow-title .pill{
font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase;
padding:4px 10px; border-radius:999px;
}
.pill.bad{background:#fee2e2; color:#991b1b}
.pill.ok{background:#dcfce7; color:#14532d}
.flow-sub{color:var(--muted); font-size:14px; margin:0 0 28px}
.flow{
display:flex; align-items:flex-start; gap:8px;
overflow-x:auto; padding:8px 4px 24px;
}
.node{
flex:0 0 auto; width:108px; text-align:center;
}
.tile{
width:88px; height:88px; border-radius:20px;
margin:0 auto 12px;
display:flex; align-items:center; justify-content:center;
font-size:38px;
box-shadow:0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.04);
}
.tile.navy {background:var(--navy); color:#fff}
.tile.peach {background:var(--peach); color:var(--peach-ink)}
.tile.lavender{background:var(--lavender); color:var(--lavender-ink)}
.tile.amber {background:var(--amber); color:var(--amber-ink)}
.tile.mint {background:var(--mint); color:var(--mint-ink)}
.tile.rose {background:var(--rose); color:var(--rose-ink)}
.tile.sky {background:var(--sky); color:var(--sky-ink)}
.tile.slate {background:var(--slate); color:var(--slate-ink)}
.label{font-size:13px; font-weight:600; line-height:1.25}
.sub{font-size:11px; color:var(--muted); margin-top:3px; line-height:1.3}
.arrow{
flex:0 0 auto; align-self:center;
color:#cbd5e1; font-size:22px; padding:0 2px; margin-top:-36px;
user-select:none;
}
.group{
border:1.5px dashed #cbd5e1; border-radius:18px;
padding:18px 14px 14px; position:relative;
margin-top:14px;
display:flex; align-items:flex-start; gap:4px;
}
.group::before{
content:attr(data-label);
position:absolute; top:-9px; left:18px;
background:#fff; padding:0 8px;
font-size:10px; font-weight:700; letter-spacing:1.5px; text-transform:uppercase;
color:var(--muted);
}
.divider{
height:1px; background:var(--line);
margin:32px 0;
}
/* metrics */
.metrics{
display:grid; grid-template-columns:repeat(4,1fr); gap:16px;
margin-top:8px;
}
.metric{
background:var(--soft); border:1px solid var(--line); border-radius:14px;
padding:18px;
}
.metric .m-label{font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:1.2px; font-weight:600}
.metric .m-row{display:flex; align-items:baseline; gap:10px; margin-top:8px}
.metric .m-before{font-size:14px; color:#94a3b8; text-decoration:line-through}
.metric .m-after{font-size:22px; font-weight:700; color:var(--ink)}
.metric .m-arrow{color:#cbd5e1; font-size:14px}
/* footer two-column */
.twocol{display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-top:32px}
.card{
border:1px solid var(--line); border-radius:14px; padding:20px;
}
.card h3{margin:0 0 10px; font-size:14px; display:flex; align-items:center; gap:8px}
.card ul{margin:0; padding-left:18px; font-size:13px; color:var(--slate-ink); line-height:1.6}
.card .dot{width:8px; height:8px; border-radius:50%; display:inline-block}
.dot.red{background:#ef4444} .dot.green{background:#10b981}
@media (max-width:1100px){
.metrics{grid-template-columns:1fr 1fr}
.twocol{grid-template-columns:1fr}
body{padding:32px 24px}
}
</style>
</head>
<body>
<h1>Balam · Flujo financiero</h1>
<p class="lede">Cómo opera el ciclo de facturación, cobranza y conciliación hoy, y cómo operará después del MVP.</p>
<!-- ====== HOY ====== -->
<div class="flow-block">
<div class="flow-title">Hoy <span class="pill bad">Manual</span></div>
<p class="flow-sub">Ocho pasos, todos dependientes de una persona moviendo datos entre sistemas que no se hablan.</p>
<p class="section-label">Flujo actual</p>
<div class="flow">
<div class="node">
<div class="tile slate">🕐</div>
<div class="label">Jira</div>
<div class="sub">Horas por cliente</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile slate">👥</div>
<div class="label">BUK</div>
<div class="sub">Nómina aprobada</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">✍️</div>
<div class="label">Captura factura</div>
<div class="sub">Manual en BIND</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">📧</div>
<div class="label">Cobranza</div>
<div class="sub">Correo a mano</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">⬇️</div>
<div class="label">PDFs banco</div>
<div class="sub">Descarga manual</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">📊</div>
<div class="label">Excel</div>
<div class="sub">Conciliación</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">📒</div>
<div class="label">Contador</div>
<div class="sub">Captura asientos</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile rose">📈</div>
<div class="label">Reporte CxC</div>
<div class="sub">Llega tarde</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- ====== DESPUÉS ====== -->
<div class="flow-block">
<div class="flow-title">Después del MVP <span class="pill ok">Orquestado</span></div>
<p class="flow-sub">La plataforma absorbe los pasos repetitivos. Las personas solo tocan excepciones.</p>
<p class="section-label">Diagrama de flujo completo</p>
<div class="flow">
<div class="node">
<div class="tile navy">📘</div>
<div class="label">BIND ERP</div>
<div class="sub">Fuente de verdad</div>
</div>
<div class="arrow"></div>
<!-- Plataforma group -->
<div class="group" data-label="Plataforma de operaciones financieras">
<div class="node">
<div class="tile peach">🔄</div>
<div class="label">Sync API</div>
<div class="sub">Facturas + clientes</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile lavender">⚙️</div>
<div class="label">Motor</div>
<div class="sub">Reglas + match</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile lavender">📬</div>
<div class="label">Cobranza</div>
<div class="sub">Auto + lista blanca</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile amber">📊</div>
<div class="label">Dashboard</div>
<div class="sub">CxC en vivo</div>
</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile mint">💳</div>
<div class="label">Pago link</div>
<div class="sub">Stripe · opcional</div>
</div>
</div>
<p class="section-label" style="margin-top:32px">Ramal de conciliación bancaria</p>
<div class="flow">
<div class="node">
<div class="tile slate">📄</div>
<div class="label">PDF banco</div>
<div class="sub">Upload mensual</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile peach">🤖</div>
<div class="label">Claude</div>
<div class="sub">Extrae movimientos</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile lavender">🔗</div>
<div class="label">Match</div>
<div class="sub">Auto · monto + ref</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile amber">👁️</div>
<div class="label">Revisión</div>
<div class="sub">Solo excepciones</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="tile mint">📥</div>
<div class="label">Export BIND</div>
<div class="sub">Asientos al contador</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- ====== MÉTRICAS ====== -->
<p class="section-label">Impacto medible</p>
<div class="metrics">
<div class="metric">
<div class="m-label">Pasos manuales</div>
<div class="m-row">
<span class="m-before">8</span>
<span class="m-arrow"></span>
<span class="m-after">2</span>
</div>
</div>
<div class="metric">
<div class="m-label">Tiempo de conciliación</div>
<div class="m-row">
<span class="m-before">Horas / mes</span>
<span class="m-arrow"></span>
<span class="m-after">60%</span>
</div>
</div>
<div class="metric">
<div class="m-label">Errores contables</div>
<div class="m-row">
<span class="m-before">Recurrentes</span>
<span class="m-arrow"></span>
<span class="m-after">70%</span>
</div>
</div>
<div class="metric">
<div class="m-label">Visibilidad dirección</div>
<div class="m-row">
<span class="m-before">Reporte</span>
<span class="m-arrow"></span>
<span class="m-after">Tiempo real</span>
</div>
</div>
</div>
<!-- ====== DOLOR / GANANCIA ====== -->
<div class="twocol">
<div class="card">
<h3><span class="dot red"></span> Dolor que se elimina</h3>
<ul>
<li>Riesgo de mandar recordatorio a ACUNTIA o Top 3 por error humano.</li>
<li>Horas perdidas conciliando 3 PDFs bancarios a mano.</li>
<li>Errores contables que llegan tarde al contador.</li>
<li>Dirección sin visibilidad real del CxC del día.</li>
<li>Cobranza dependiendo de que alguien recuerde mandar el correo.</li>
</ul>
</div>
<div class="card">
<h3><span class="dot green"></span> Lo que se gana</h3>
<ul>
<li>Cobro más rápido con link de pago directo en el correo.</li>
<li>Conciliación automática + cola humana solo para excepciones.</li>
<li>Dashboard en tiempo real accesible para CEO / CFO / CTO.</li>
<li>Audit log completo de quién hizo qué y cuándo.</li>
<li>Base preparada para Fase 2: Jira → BUK → Factura end-to-end.</li>
</ul>
</div>
</div>
</body>
</html>
@@ -0,0 +1,315 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Balam — Flujo HOY vs DESPUÉS del MVP</title>
<style>
:root{
--bg:#0f172a;
--text:#e2e8f0; --muted:#94a3b8;
--bad:#ef4444; --bad-bg:rgba(239,68,68,.12);
--ok:#10b981; --ok-bg:rgba(16,185,129,.12);
--warn:#f59e0b;
--info:#3b82f6;
--accent:#a78bfa;
--border:#475569;
}
*{box-sizing:border-box}
body{
margin:0; padding:32px;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 100%);
color:var(--text); min-height:100vh;
}
h1{margin:0 0 4px; font-size:28px}
.subtitle{color:var(--muted); margin-bottom:24px; font-size:14px}
.legend{
display:flex; gap:16px; flex-wrap:wrap; margin-bottom:24px;
padding:10px 14px; background:rgba(0,0,0,.25); border-radius:8px;
font-size:12px;
}
.legend-item{display:flex; align-items:center; gap:6px}
.icon{font-size:14px}
.columns{display:grid; grid-template-columns:1fr 1fr; gap:24px}
@media (max-width:1100px){ .columns{grid-template-columns:1fr} }
.panel{
background:#1e293b; border:1px solid var(--border);
border-radius:14px; padding:20px;
box-shadow:0 4px 16px rgba(0,0,0,.3);
}
.panel.before{border-top:4px solid var(--bad)}
.panel.after{border-top:4px solid var(--ok)}
.panel h2{
margin:0 0 4px; font-size:18px; display:flex; align-items:center; gap:10px;
}
.panel .tag{
font-size:11px; font-weight:700; padding:3px 10px; border-radius:10px;
text-transform:uppercase; letter-spacing:1px;
}
.tag.bad{background:var(--bad-bg); color:#fca5a5}
.tag.ok{background:var(--ok-bg); color:#6ee7b7}
.panel .lead{color:var(--muted); font-size:13px; margin-bottom:16px}
/* flow */
.flow{display:flex; flex-direction:column; gap:6px}
.step{
background:#334155; border:1px solid var(--border); border-radius:10px;
padding:10px 14px; font-size:13px; line-height:1.4;
display:flex; gap:10px; align-items:flex-start;
}
.step .num{
flex-shrink:0; width:24px; height:24px; border-radius:50%;
background:#475569; color:#fff; font-weight:700; font-size:12px;
display:flex; align-items:center; justify-content:center;
}
.step .body{flex:1}
.step .who{display:block; font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px; margin-bottom:2px}
.step .what{color:var(--text)}
.step .note{display:block; font-size:11px; color:var(--muted); margin-top:4px; font-style:italic}
.step.manual .num{background:var(--bad)}
.step.manual{border-left:3px solid var(--bad)}
.step.auto .num{background:var(--ok)}
.step.auto{border-left:3px solid var(--ok)}
.step.semi .num{background:var(--warn)}
.step.semi{border-left:3px solid var(--warn)}
.arrow-down{
text-align:center; color:var(--muted); font-size:18px; line-height:1;
margin:-2px 0;
}
/* metrics */
.metrics{
display:grid; grid-template-columns:repeat(4,1fr); gap:12px;
margin-top:24px;
}
.metric{
background:#1e293b; border:1px solid var(--border); border-radius:12px;
padding:14px; text-align:center;
}
.metric .label{font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:1px}
.metric .before-val{font-size:18px; color:#fca5a5; margin-top:4px; text-decoration:line-through; opacity:.7}
.metric .arrow{font-size:14px; color:var(--accent); margin:2px 0}
.metric .after-val{font-size:20px; color:#6ee7b7; font-weight:700}
/* pain & gain box */
.summary{
display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:24px;
}
.summary .box{
border-radius:12px; padding:16px;
}
.summary .pain{background:var(--bad-bg); border-left:4px solid var(--bad)}
.summary .gain{background:var(--ok-bg); border-left:4px solid var(--ok)}
.summary h3{margin:0 0 10px; font-size:14px}
.summary ul{margin:0; padding-left:20px; font-size:13px; line-height:1.6; color:var(--text)}
@media (max-width:700px){
.metrics{grid-template-columns:1fr 1fr}
.summary{grid-template-columns:1fr}
}
</style>
</head>
<body>
<h1>Balam · Flujo Financiero</h1>
<div class="subtitle">Cómo opera HOY vs cómo operará DESPUÉS del MVP</div>
<div class="legend">
<div class="legend-item"><span class="icon">🔴</span> Paso manual / handoff humano</div>
<div class="legend-item"><span class="icon">🟡</span> Semi-automático (humano valida)</div>
<div class="legend-item"><span class="icon">🟢</span> Automático</div>
</div>
<div class="columns">
<!-- ================= HOY ================= -->
<div class="panel before">
<h2>🕰️ HOY <span class="tag bad">Manual y desconectado</span></h2>
<div class="lead">Cada paso depende de una persona moviendo datos entre sistemas que no se hablan.</div>
<div class="flow">
<div class="step manual"><div class="num">1</div><div class="body">
<span class="who">Jira · Operaciones</span>
<span class="what">Registro de horas por colaborador y cliente.</span>
<span class="note">Sin visibilidad consolidada para facturación.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">2</div><div class="body">
<span class="who">BUK · RH</span>
<span class="what">Nómina aprobada de los 45 colaboradores + 5 freelancers.</span>
<span class="note">Sin trigger automático hacia facturación.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">3</div><div class="body">
<span class="who">Persona · Admin</span>
<span class="what">Genera factura manualmente en BIND ERP por cada cliente (~50/mes).</span>
<span class="note">Riesgo de error en monto, RFC, concepto.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">4</div><div class="body">
<span class="who">Persona · Cobranza</span>
<span class="what">Recordatorios manuales por correo a cada cliente vencido.</span>
<span class="note">Riesgo de enviar accidentalmente a ACUNTIA / Top 3 → daño de relación.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">5</div><div class="body">
<span class="who">Persona · Finanzas</span>
<span class="what">Descarga 3 PDFs bancarios (2 MX + IBC Texas) desde portales web.</span>
<span class="note">Producción únicamente, sin API.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">6</div><div class="body">
<span class="who">Persona · Finanzas</span>
<span class="what">Concilia movimiento por movimiento contra facturas en Excel.</span>
<span class="note">Horas de trabajo / mes · errores recurrentes.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">7</div><div class="body">
<span class="who">Contador</span>
<span class="what">Captura asientos contables en BIND a partir del Excel conciliado.</span>
<span class="note">Retrabajo si la conciliación previa tuvo errores.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">8</div><div class="body">
<span class="who">Dirección</span>
<span class="what">Pide reporte de CxC al área. Llega tarde y desactualizado.</span>
<span class="note">Sin visibilidad en tiempo real.</span>
</div></div>
</div>
</div>
<!-- ================= DESPUÉS ================= -->
<div class="panel after">
<h2>🚀 DESPUÉS del MVP <span class="tag ok">Orquestado y visible</span></h2>
<div class="lead">La plataforma absorbe los pasos repetitivos. Las personas solo deciden en excepciones.</div>
<div class="flow">
<div class="step semi"><div class="num">1</div><div class="body">
<span class="who">BIND ERP (API) → Plataforma</span>
<span class="what">Sync automático de facturas, clientes y catálogo.</span>
<span class="note">BIND sigue siendo fuente de verdad y emite CFDI.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step auto"><div class="num">2</div><div class="body">
<span class="who">Plataforma · Dashboard</span>
<span class="what">CxC en tiempo real: vencidas, próximas, por cliente, multimoneda (MXN/USD).</span>
<span class="note">Dirección entra al dashboard cuando quiera, sin pedir reporte.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step auto"><div class="num">3</div><div class="body">
<span class="who">Plataforma · Cobranza</span>
<span class="what">Recordatorios automáticos por correo 5 días antes del vencimiento.</span>
<span class="note"><strong>Lista blanca dura:</strong> ACUNTIA + Top 3 NUNCA reciben automático.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step auto"><div class="num">4</div><div class="body">
<span class="who">Plataforma · Pago (opcional)</span>
<span class="what">Link de pago Stripe en el correo → tarjeta o SPEI.</span>
<span class="note">Webhook marca factura como pagada al recibir confirmación.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step manual"><div class="num">5</div><div class="body">
<span class="who">Persona · Finanzas (mínimo)</span>
<span class="what">Sube 3 PDFs bancarios a la plataforma (mensual).</span>
<span class="note">Único paso manual que sigue. Se elimina en Fase 2 con Belvo/Plaid.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step auto"><div class="num">6</div><div class="body">
<span class="who">Plataforma · IA (Claude)</span>
<span class="what">Extrae movimientos de los PDFs y valida totales.</span>
<span class="note">Una pipeline por banco · datos no se usan para entrenamiento.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step semi"><div class="num">7</div><div class="body">
<span class="who">Plataforma · Motor conciliación</span>
<span class="what">Match automático monto+referencia+fecha. No-match → cola de revisión humana.</span>
<span class="note">Persona solo toca las excepciones, no todo.</span>
</div></div>
<div class="arrow-down"></div>
<div class="step semi"><div class="num">8</div><div class="body">
<span class="who">Contador · BIND</span>
<span class="what">Sube a BIND el export Excel ya conciliado y clasificado.</span>
<span class="note">Asientos automáticos contra BIND API → Fase 2.</span>
</div></div>
</div>
</div>
</div>
<!-- ================= MÉTRICAS ================= -->
<div class="metrics">
<div class="metric">
<div class="label">Pasos manuales</div>
<div class="before-val">8 de 8</div>
<div class="arrow"></div>
<div class="after-val">2 de 8</div>
</div>
<div class="metric">
<div class="label">Tiempo conciliación bancaria</div>
<div class="before-val">Horas / mes</div>
<div class="arrow"></div>
<div class="after-val">60% mín.</div>
</div>
<div class="metric">
<div class="label">Errores contables</div>
<div class="before-val">Recurrentes</div>
<div class="arrow"></div>
<div class="after-val">70% mín.</div>
</div>
<div class="metric">
<div class="label">Visibilidad para dirección</div>
<div class="before-val">Reporte pedido</div>
<div class="arrow"></div>
<div class="after-val">Tiempo real</div>
</div>
</div>
<!-- ================= DOLOR / GANANCIA ================= -->
<div class="summary">
<div class="box pain">
<h3>🔴 Dolor que se elimina</h3>
<ul>
<li>Riesgo de enviar recordatorio a ACUNTIA o Top 3 por error humano.</li>
<li>Horas perdidas conciliando 3 PDFs bancarios a mano.</li>
<li>Errores contables que llegan tarde al contador.</li>
<li>Dirección sin visibilidad real del CxC del día.</li>
<li>Cobranza dependiendo de que alguien recuerde mandar el correo.</li>
</ul>
</div>
<div class="box gain">
<h3>🟢 Lo que se gana</h3>
<ul>
<li>Cobro más rápido con link Stripe directo en el correo.</li>
<li>Conciliación automática + cola humana solo para excepciones.</li>
<li>Dashboard en tiempo real accesible para CEO/CFO/CTO.</li>
<li>Audit log completo de quién hizo qué, cuándo.</li>
<li>Base preparada para Fase 2: Jira→BUK→Factura end-to-end.</li>
</ul>
</div>
</div>
</body>
</html>
+293
View File
@@ -0,0 +1,293 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Balam — Qué se necesita para construir la plataforma</title>
<style>
:root{
--bg:#0f172a; --panel:#1e293b; --panel2:#334155;
--ok:#10b981; --warn:#f59e0b; --bad:#ef4444; --info:#3b82f6;
--text:#e2e8f0; --muted:#94a3b8; --accent:#a78bfa;
--border:#475569;
}
*{box-sizing:border-box}
body{
margin:0; padding:32px;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 100%);
color:var(--text); min-height:100vh;
}
h1{font-size:28px; margin:0 0 4px}
.subtitle{color:var(--muted); margin-bottom:32px; font-size:14px}
.legend{
display:flex; gap:16px; flex-wrap:wrap; margin-bottom:24px;
padding:12px 16px; background:rgba(0,0,0,.25); border-radius:8px;
font-size:13px;
}
.legend-item{display:flex; align-items:center; gap:6px}
.dot{width:12px; height:12px; border-radius:50%}
.dot.ok{background:var(--ok)} .dot.warn{background:var(--warn)}
.dot.bad{background:var(--bad)} .dot.info{background:var(--info)}
.dot.accent{background:var(--accent)}
.grid{
display:grid; grid-template-columns:1fr 1fr 1fr; gap:24px;
margin-bottom:24px;
}
.row{
display:grid; grid-template-columns:1fr 2fr 1fr; gap:24px;
align-items:stretch; margin-bottom:24px;
}
.col{display:flex; flex-direction:column; gap:16px}
.card{
background:var(--panel); border:1px solid var(--border);
border-radius:12px; padding:16px;
box-shadow:0 4px 12px rgba(0,0,0,.3);
}
.card h3{margin:0 0 8px; font-size:14px; display:flex; align-items:center; gap:8px}
.card .badge{
font-size:10px; padding:2px 8px; border-radius:10px;
background:var(--panel2); color:var(--muted); font-weight:600;
text-transform:uppercase; letter-spacing:.5px;
}
.badge.ok{background:rgba(16,185,129,.2); color:#6ee7b7}
.badge.warn{background:rgba(245,158,11,.2); color:#fcd34d}
.badge.bad{background:rgba(239,68,68,.2); color:#fca5a5}
.badge.info{background:rgba(59,130,246,.2); color:#93c5fd}
.badge.accent{background:rgba(167,139,250,.2); color:#c4b5fd}
.card ul{margin:6px 0 0; padding-left:18px; font-size:12px; color:var(--muted)}
.card li{margin:3px 0; line-height:1.4}
.section-title{
font-size:11px; font-weight:700; text-transform:uppercase;
letter-spacing:1.5px; color:var(--muted); margin:32px 0 12px;
padding-bottom:8px; border-bottom:1px solid var(--border);
}
.core-card{
background:linear-gradient(135deg,#4c1d95 0%,#7c3aed 100%);
border:2px solid var(--accent);
text-align:center; padding:24px;
}
.core-card h2{margin:0 0 8px; font-size:18px}
.core-card p{margin:0; font-size:13px; color:#ddd6fe}
.arrow{
display:flex; align-items:center; justify-content:center;
font-size:24px; color:var(--accent);
}
.checklist{
background:rgba(0,0,0,.3); border-left:4px solid var(--warn);
padding:16px 20px; border-radius:8px; margin-top:16px;
}
.checklist h3{margin:0 0 12px; font-size:14px; color:var(--warn)}
.checklist ol{margin:0; padding-left:20px; font-size:13px; line-height:1.7}
.checklist li strong{color:#fcd34d}
.footer-grid{display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:16px}
.footer-card{background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:16px}
.footer-card h4{margin:0 0 8px; font-size:13px}
.footer-card p, .footer-card ul{font-size:12px; color:var(--muted); margin:0; line-height:1.5}
.footer-card ul{padding-left:18px}
.pill{
display:inline-block; padding:2px 8px; border-radius:6px;
background:var(--panel2); font-size:11px; margin-right:4px;
}
@media (max-width:900px){
.grid,.row{grid-template-columns:1fr}
.arrow{transform:rotate(90deg)}
}
</style>
</head>
<body>
<h1>Balam · Plataforma de Automatización Financiera</h1>
<div class="subtitle">¿Qué se necesita para construirla? — vista de un solo vistazo</div>
<div class="legend">
<div class="legend-item"><span class="dot ok"></span> Ya existe / disponible</div>
<div class="legend-item"><span class="dot warn"></span> Por confirmar con Balam</div>
<div class="legend-item"><span class="dot bad"></span> Bloqueador si falta</div>
<div class="legend-item"><span class="dot info"></span> Lo construyo yo</div>
<div class="legend-item"><span class="dot accent"></span> Servicio externo (costo Balam)</div>
</div>
<!-- ============ FILA 1: FUENTES DE DATOS ============ -->
<div class="section-title">1 · Fuentes de datos (de dónde sale la información)</div>
<div class="grid">
<div class="card">
<h3>BIND ERP <span class="badge ok">API confirmada</span></h3>
<ul>
<li>Facturas, clientes, catálogo</li>
<li>Timbrado CFDI con PAC integrado</li>
<li>Límite 20K req/día (suficiente)</li>
<li><strong>Falta confirmar:</strong> sandbox, webhooks, endpoints de escritura</li>
</ul>
</div>
<div class="card">
<h3>3 Bancos (PDFs) <span class="badge warn">Solo PDF hoy</span></h3>
<ul>
<li>2 bancos MX (Banorte + Intercam?)</li>
<li>1 banco US: IBC Bank Texas</li>
<li>Sin API en MVP, sin sandbox</li>
<li><strong>Falta:</strong> muestras reales anonimizadas</li>
</ul>
</div>
<div class="card">
<h3>BUK / Jira <span class="badge bad">Diferido Fase 2</span></h3>
<ul>
<li>Nómina (BUK) y horas (Jira)</li>
<li>BUK: servicio problemático, API por confirmar</li>
<li>NO entran en MVP</li>
<li>Habilitan flujo end-to-end más adelante</li>
</ul>
</div>
</div>
<!-- ============ FILA 2: LA PLATAFORMA ============ -->
<div class="section-title">2 · La plataforma que construyo (capa sobre BIND, no reemplazo)</div>
<div class="card core-card">
<h2>⚙️ Plataforma de Operaciones Financieras</h2>
<p>Orquesta el flujo entre BIND, bancos y el equipo · No emite CFDI · No reemplaza contabilidad</p>
</div>
<div class="grid" style="margin-top:16px">
<div class="card">
<h3>📥 Ingesta &amp; Sync <span class="badge info">Construir</span></h3>
<ul>
<li>Conector BIND API (lectura)</li>
<li>Upload de PDFs bancarios</li>
<li>Parser IA (Claude) para PDFs</li>
<li>Validación de totales</li>
</ul>
</div>
<div class="card">
<h3>🔁 Motor de negocio <span class="badge info">Construir</span></h3>
<ul>
<li>Conciliación pagos ↔ facturas</li>
<li>Cobranza con lista blanca dura</li>
<li>Templates de correo + recordatorios</li>
<li>Audit log universal</li>
</ul>
</div>
<div class="card">
<h3>📊 UI &amp; Reportes <span class="badge info">Construir</span></h3>
<ul>
<li>Dashboard CxC / vencidas / próximas</li>
<li>RBAC: Finanzas / Dirección / Ops</li>
<li>Export Excel para BIND</li>
<li>Multimoneda MXN + USD (TC DOF)</li>
</ul>
</div>
</div>
<!-- ============ FILA 3: SERVICIOS EXTERNOS ============ -->
<div class="section-title">3 · Servicios externos (Balam los paga directo)</div>
<div class="grid">
<div class="card">
<h3>☁️ Azure <span class="badge accent">$45$140 USD/mes</span></h3>
<ul>
<li>App Service + worker</li>
<li>PostgreSQL administrado</li>
<li>Blob Storage para PDFs</li>
<li><strong>Falta:</strong> ¿subscripción existe?</li>
</ul>
</div>
<div class="card">
<h3>🤖 Claude API <span class="badge accent">$5$20 USD/mes</span></h3>
<ul>
<li>Parsing de PDFs bancarios</li>
<li>Sin entrenamiento sobre datos del cliente</li>
<li>Costo por página procesada</li>
</ul>
</div>
<div class="card">
<h3>💳 Stripe MX <span class="badge accent">% por transacción</span></h3>
<ul>
<li>Solo si confirman pago con link</li>
<li>RFC + CLABE + rep. legal requeridos</li>
<li><strong>Falta confirmar:</strong> ¿lo quieren?</li>
</ul>
</div>
</div>
<!-- ============ FILA 4: LO QUE NECESITO DE BALAM ============ -->
<div class="section-title">4 · Lo que Balam debe entregar antes / durante Fase 0</div>
<div class="grid">
<div class="card">
<h3>👥 Personas <span class="badge warn">Por nombrar</span></h3>
<ul>
<li>1 contacto técnico (día a día)</li>
<li>1 gerente administrativo (reglas)</li>
<li>Admin de BIND para resolver dudas</li>
<li>SLA respuesta &lt;48 h en bloqueadores</li>
</ul>
</div>
<div class="card">
<h3>🔑 Accesos <span class="badge warn">Crítico</span></h3>
<ul>
<li>API Key de BIND (o gestionarlo)</li>
<li>Subscripción Azure</li>
<li>Acceso a portal IBC para descarga PDFs</li>
<li>Cuenta Stripe MX (si aplica)</li>
</ul>
</div>
<div class="card">
<h3>📄 Datos &amp; reglas <span class="badge warn">Inicio Fase 0</span></h3>
<ul>
<li>3 PDFs reales (1 por banco) anonimizados</li>
<li>Lista blanca: ACUNTIA + Top 3 con nombres legales</li>
<li>Manual de marca</li>
<li>NDA (suyo o mío)</li>
<li>Ciclo de cobranza actual (días, escalación)</li>
</ul>
</div>
</div>
<!-- ============ DECISIÓN PENDIENTE ============ -->
<div class="checklist">
<h3>⚠️ Decisión pendiente del CTO antes de cotizar firme</h3>
<ol>
<li><strong>Alcance del MVP:</strong> ¿Opción A "BIND-first" (34 semanas, ~$3645K MXN) o Opción B "MVP completo con conciliación bancaria" (6 semanas, ~$6684K MXN)?</li>
<li><strong>BIND API:</strong> sandbox disponible (aunque sea pagado), webhooks expuestos, endpoints de escritura (registrar pagos / asientos).</li>
<li><strong>Pago con link Stripe:</strong> ¿es requerimiento real o se difiere a Fase 2?</li>
<li><strong>Operación:</strong> nombres de contacto técnico y gerente administrativo + cuenta Azure.</li>
</ol>
</div>
<!-- ============ FOOTER: PARÁMETROS ============ -->
<div class="footer-grid">
<div class="footer-card">
<h4>💰 Modelo comercial</h4>
<ul>
<li><span class="pill">600 MXN/h</span> + IVA, facturación semanal</li>
<li>Time &amp; Materials con soft cap por fase</li>
<li>Pago a 7 días, anticipo de 30 h Fase 0</li>
<li>Dedicación medio tiempo (~20 h/semana)</li>
</ul>
</div>
<div class="footer-card">
<h4>⏱️ Tiempo &amp; entrega</h4>
<ul>
<li>MVP B completo: 6 semanas esfuerzo · 8 semanas calendario</li>
<li>MVP A BIND-first: 34 semanas esfuerzo</li>
<li>Soporte post-launch: 2 semanas incluidas</li>
<li>Demo semanal viernes + standup async 2-3/sem</li>
</ul>
</div>
</div>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
# --- Configuración del sandbox de BIND ---
#
# Por default el cliente apunta al mock local (puerto 4010).
# Cuando Pedro entregue el API key real, copia este archivo a .env y
# cambia BIND_BASE_URL a https://api.bind.com.mx + llena las credenciales.
#
# Recuerda: BIND solo tiene PRODUCCIÓN. Cualquier llamada con base URL real
# afecta datos reales de Balam. Mantén MODE=read-only mientras no haya
# autorización explícita para escribir.
BIND_BASE_URL=http://localhost:4010
BIND_API_KEY=mock-bearer-token
BIND_SUBSCRIPTION_KEY=mock-subscription-key
# read-only | dry-run | write
# - read-only: solo GET. Bloquea POST/PUT/PATCH/DELETE en el cliente.
# - dry-run: loguea el request que se haría pero no lo envía.
# - write: emite escrituras reales. Solo en mock o con autorización.
BIND_MODE=read-only
# Puerto del mock server
MOCK_PORT=4010
+4
View File
@@ -0,0 +1,4 @@
node_modules/
dist/
.env
*.log
+159
View File
@@ -0,0 +1,159 @@
# BIND ERP API · sandbox local
Sandbox para validar la integración del MVP **BIND-first** de Balam **sin tocar producción**.
Está pensado para que tú (Johann), Pedro (Balam) o un futuro dev puedan:
1. Entender la forma real del API de BIND ERP antes de tener el API key.
2. Probar el cliente tipado contra un mock que replica los headers, el formato OData y el rate-limit observable de BIND.
3. Cuando Pedro entregue las credenciales reales, **cambiar dos variables de entorno** y apuntar el mismo código a `https://api.bind.com.mx` sin reescribir nada.
---
## TL;DR del API de BIND (lo que descubrí del discovery)
| Tema | Hallazgo |
|---|---|
| **Base URL** | `https://api.bind.com.mx` |
| **Estilo** | REST con sintaxis **OData v3** (filtros `$filter`, `$top`, `$skip`, `$orderby`, `$count`, IDs como `guid'...'`) |
| **Auth** | Dos headers: `Authorization: Bearer <API_KEY>` + `Ocp-Apim-Subscription-Key: <SUBSCRIPTION_KEY>` (este último cuando aplica) |
| **Origen del API key** | Cuenta BIND → Perfil de usuario → pestaña *Integraciones* |
| **Rate limit** | **20,000 peticiones / día** (confirmado por Noe, 25-may-2026) |
| **Sandbox oficial** | **No existe.** BIND recomienda Postman contra producción → razón #1 de este sandbox |
| **Portal dev** | [developers.bind.com.mx](https://developers.bind.com.mx) (login requerido para ver schemas detallados) |
| **PAC para CFDI** | Integrado dentro del propio BIND — la plataforma de Balam **no toca el SAT**, solo orquesta |
| **Recursos confirmados** | `Activities`, `Customers`, `Products` (y según el discovery doc: `Invoices`, `Payments`, `Quotes` muy probables) |
### Por qué un sandbox propio y no Postman
- BIND solo tiene producción → cualquier `POST` real toca facturas reales con consecuencias fiscales.
- El contrato exacto está detrás de login → necesitamos un lugar donde ir **acumulando lo que aprendemos** del API real conforme Pedro nos dé acceso.
- Tener el contrato en código (TypeScript + tipos) hace que **el motor de cobranza del MVP se pueda probar con CI** sin depender de la red.
- Cuando llegue Belvo / BUK en Fase 2 esta misma estructura sirve como plantilla.
---
## Cómo correrlo
Requiere Node 20+.
```powershell
# 1) Instalar deps
cd bind-api-sandbox
npm install
# 2) Levantar el mock en una terminal
npm run mock
# -> [bind-mock] escuchando en http://localhost:4010
# 3) Correr la demo end-to-end en otra terminal
npm run demo
```
La demo ejecuta 5 escenarios alineados al MVP:
| # | Escenario | Qué demuestra |
|---|---|---|
| 1 | Listar clientes activos | Cómo se construye la query OData base del dashboard |
| 2 | Facturas vencidas (`Status eq 'overdue' and DueDate lt ...`) | El motor de cobranza |
| 3 | CxC agregada por cliente (MXN) | KPI directivo del dashboard |
| 4 | Factura USD a cliente extranjero | Regla sin-IVA + TC fijado al emitir |
| 5 | Intento de `POST /Activities` en modo read-only | El guardrail que evita escribir a BIND prod por accidente |
Output esperado (verificado):
```
── 2. Facturas vencidas — motor de cobranza
┌─────────┬──────────┬────────────┬──────────────┬──────────┬─────────┐
│ Folio │ Cliente │ DueDate │ Currency │ Balance │
│ 'A-0003' │ '22222222' │ '2026-04-01' │ 'MXN' │ 20880 │
│ 'A-0005' │ '11111111' │ '2026-05-20' │ 'MXN' │ 62640 │
── 5. Intento de escritura en read-only (debe BLOQUEARSE)
✅ Guardrail OK: Read-only mode bloqueó POST /api/Activities. Cambia BIND_MODE=write...
Stats del cliente
{ requestsToday: 6, quota: 20000, remaining: 19994, mode: 'read-only' }
```
### Apuntar a producción (cuando llegue el API key)
Solo cambiar variables de entorno — el código no se modifica:
```powershell
$env:BIND_BASE_URL = "https://api.bind.com.mx"
$env:BIND_API_KEY = "<key del perfil de usuario de Balam>"
$env:BIND_SUBSCRIPTION_KEY = "<si aplica>"
$env:BIND_MODE = "read-only" # mantenlo así hasta tener autorización para escribir
npm run demo
```
---
## Mapeo a la arquitectura de Balam
Este sandbox es el prototipo de lo que en el repo principal vivirá en `packages/integrations/bind/`:
```
balam/
└── packages/
└── integrations/
└── bind/
├── BindClient.ts ← este sandbox lo prototipa
├── types.ts ← este sandbox lo prototipa
├── odata.ts ← este sandbox lo prototipa
└── README.md
apps/
└── worker/
└── src/
└── modules/
└── bind-sync/ ← consume BindClient, escribe a Postgres,
respeta tenant_id + outbox pattern
```
El cliente está pensado para encajar con los principios del proyecto (ver `01 - ARQUITECTURA-TECNICA.md`):
- **Modo seguro por default** (`read-only`): bloquea `POST/PUT/PATCH/DELETE` en código. Cumple §1 "La plataforma no escribe a BIND en MVP".
- **Dry-run** opcional: imprime el request sin enviarlo (cumple §1 punto 2 "Modo dry-run disponible en cualquier acción con efecto externo").
- **Idempotencia preparada**: el método `addActivity` está aislado para que cuando se autorice escritura, sea fácil envolverlo con `idempotency_key`.
- **Quota awareness**: el cliente lleva un contador local de requests del día y avisa cuando se acerca al límite de 20K.
- **Retries con backoff** en 429 y 5xx, respetando `Retry-After`.
---
## Decisiones de discovery que este sandbox **acelera**
Estos son los puntos del `03_Anexo_Tecnico_Integraciones_Discovery_Balam.docx` y del `04_Checklist_Accesos_Datos_Dependencias_Balam.docx` que dejan de estar "pendientes de validar" en cuanto se ejecuta esta prueba con un API key real:
| Item del discovery | Cómo lo cierra este sandbox |
|---|---|
| Tipo de autenticación, headers requeridos | Ya implementado en `BindClient`: dos headers, listos para producción |
| Estructura de URLs por recurso | Confirmada (`/api/{Recurso}` + OData) y probada en mock |
| Operaciones de lectura: clientes, facturas, pagos | Demo las ejerce todas. Una vez con API key, basta correr `BIND_BASE_URL=https://api.bind.com.mx npm run demo` |
| Filtros y paginación (`$filter`, `$top`, `$skip`) | Validados contra mock con la misma sintaxis que documenta BIND |
| Estrategia de pruebas sin sandbox | **Esta es la respuesta**: mock local + cliente tipado + modos read-only / dry-run / write |
| Rate limits | El cliente cuenta requests; el mock simula `?simulate=throttle` para probar el backoff |
---
## Pendientes para cerrar con Pedro (sugerencia de mail)
> Pedro, para destrabar la integración con BIND necesitamos:
>
> 1. **API key** generado desde *Perfil → Integraciones* en la cuenta de Balam, idealmente con permisos **solo lectura** primero.
> 2. **Subscription Key** si el plan de Balam la requiere (algunos planes en Azure API Management la piden además del Bearer).
> 3. Confirmación del **plan contratado** — para saber si 20K req/día aplica o si está reducido.
> 4. **Schema exacto** del recurso `Invoices` (especialmente nombres de campos `UUID`, `Folio`, `Status`, `Balance`). Una llamada de ejemplo con una factura real anonimizada serviría: `GET /api/Invoices?$top=1`.
> 5. ¿Existe endpoint para descargar **XML/PDF del CFDI**? Por la doc parece que sí, pero falta confirmar la ruta exacta.
>
> Con (1)(2) podemos correr el sandbox apuntando a `https://api.bind.com.mx` y validar lo demás de un jalón sin necesidad de otra llamada.
---
## Limitaciones honestas de este sandbox
- **El schema de `Invoice`, `Customer`, etc. es una aproximación** — está modelado a partir de la doc pública y del flujo que necesita Balam, no del SDK oficial. Cuando salga el primer `GET` real contra producción, hay que reconciliar nombres de campos (especialmente capitalización y campos opcionales).
- **El mock acepta cualquier Bearer**, solo valida que exista. No es un servidor de auth real, es un placeholder.
- **El parser de OData del mock solo cubre lo que el cliente genera** (`eq, ne, gt, lt, ge, le, and, or`, paréntesis). No soporta `contains`, `startswith`, funciones, lambdas. Suficiente para el MVP.
- **No reproduce la lógica de `$expand`** — si BIND lo soporta para traer `Lines` o `Customer` embebidos, hay que extender.
Cuando alguno de estos límites se vuelva una piedra en el zapato, se extiende. Hoy es deliberadamente mínimo.
+569
View File
@@ -0,0 +1,569 @@
{
"name": "bind-api-sandbox",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bind-api-sandbox",
"version": "0.1.0",
"devDependencies": {
"@types/node": "^22.10.0",
"tsx": "^4.19.2",
"typescript": "^5.7.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
"integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
"integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
"integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
"integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
"integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
"integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
"integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
"integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
"integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
"integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
"integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
"integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
"integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
"integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
"integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
"integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
"integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
"integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
"integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
"integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
"integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
"integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
"integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
"integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
"integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
"integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "22.19.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
"integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/esbuild": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
"integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.0",
"@esbuild/android-arm": "0.28.0",
"@esbuild/android-arm64": "0.28.0",
"@esbuild/android-x64": "0.28.0",
"@esbuild/darwin-arm64": "0.28.0",
"@esbuild/darwin-x64": "0.28.0",
"@esbuild/freebsd-arm64": "0.28.0",
"@esbuild/freebsd-x64": "0.28.0",
"@esbuild/linux-arm": "0.28.0",
"@esbuild/linux-arm64": "0.28.0",
"@esbuild/linux-ia32": "0.28.0",
"@esbuild/linux-loong64": "0.28.0",
"@esbuild/linux-mips64el": "0.28.0",
"@esbuild/linux-ppc64": "0.28.0",
"@esbuild/linux-riscv64": "0.28.0",
"@esbuild/linux-s390x": "0.28.0",
"@esbuild/linux-x64": "0.28.0",
"@esbuild/netbsd-arm64": "0.28.0",
"@esbuild/netbsd-x64": "0.28.0",
"@esbuild/openbsd-arm64": "0.28.0",
"@esbuild/openbsd-x64": "0.28.0",
"@esbuild/openharmony-arm64": "0.28.0",
"@esbuild/sunos-x64": "0.28.0",
"@esbuild/win32-arm64": "0.28.0",
"@esbuild/win32-ia32": "0.28.0",
"@esbuild/win32-x64": "0.28.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/tsx": {
"version": "4.22.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz",
"integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "bind-api-sandbox",
"private": true,
"version": "0.1.0",
"description": "Sandbox local del API de BIND ERP para validar la integración del MVP de Balam sin tocar producción.",
"type": "module",
"engines": {
"node": ">=20"
},
"scripts": {
"mock": "tsx src/mock-server/server.ts",
"demo": "tsx src/demo.ts",
"demo:prod": "BIND_BASE_URL=https://api.bind.com.mx tsx src/demo.ts",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^22.10.0",
"tsx": "^4.19.2",
"typescript": "^5.7.0"
}
}
+220
View File
@@ -0,0 +1,220 @@
/**
* Cliente del API de BIND ERP.
*
* Decisiones:
* - Modo seguro por default (read-only): bloquea cualquier método mutante.
* - dry-run: loguea el request que se haría sin enviarlo (útil para revisar
* un payload antes de aprobarlo manualmente).
* - Retries con backoff exponencial en 429 y 5xx (no en 4xx fuera de 429).
* - Lleva contador local de requests para acercarse al límite de 20K/día
* con visibilidad temprana (cuota real la valida el servidor).
* - Sin dependencias externas — usa fetch nativo de Node 20+.
*/
import { buildQueryString, type ODataQuery } from "./odata.js";
import type {
Activity,
Customer,
Invoice,
ODataCollection,
Payment,
Product,
} from "./types.js";
export type ClientMode = "read-only" | "dry-run" | "write";
export interface BindClientConfig {
baseUrl: string;
apiKey: string;
subscriptionKey?: string;
mode?: ClientMode;
/** Máximo de reintentos para 429/5xx. */
maxRetries?: number;
/** Logger opcional. Default: console. */
logger?: Pick<Console, "info" | "warn" | "error">;
/** Inyectable para tests. Default: globalThis.fetch. */
fetchImpl?: typeof fetch;
}
export class BindApiError extends Error {
constructor(
public readonly status: number,
public readonly url: string,
public readonly body: unknown,
) {
super(`BIND API ${status} on ${url}`);
}
}
export class BindReadOnlyViolation extends Error {
constructor(method: string, path: string) {
super(`Read-only mode bloqueó ${method} ${path}. Cambia BIND_MODE=write si tienes autorización.`);
}
}
const MUTATING = new Set(["POST", "PUT", "PATCH", "DELETE"]);
const DAILY_QUOTA = 20_000;
export class BindClient {
private readonly baseUrl: string;
private readonly apiKey: string;
private readonly subscriptionKey?: string;
private readonly mode: ClientMode;
private readonly maxRetries: number;
private readonly logger: Pick<Console, "info" | "warn" | "error">;
private readonly fetchImpl: typeof fetch;
private requestCount = 0;
private dayBucket = currentDayBucket();
constructor(cfg: BindClientConfig) {
this.baseUrl = cfg.baseUrl.replace(/\/+$/, "");
this.apiKey = cfg.apiKey;
this.subscriptionKey = cfg.subscriptionKey;
this.mode = cfg.mode ?? "read-only";
this.maxRetries = cfg.maxRetries ?? 3;
this.logger = cfg.logger ?? console;
this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch;
}
// --- Recursos del MVP --------------------------------------------------
customers(query: ODataQuery = {}): Promise<ODataCollection<Customer>> {
return this.get<ODataCollection<Customer>>(`/api/Customers${buildQueryString(query)}`);
}
customer(id: string): Promise<Customer> {
return this.get<Customer>(`/api/Customers(guid'${id}')`);
}
invoices(query: ODataQuery = {}): Promise<ODataCollection<Invoice>> {
return this.get<ODataCollection<Invoice>>(`/api/Invoices${buildQueryString(query)}`);
}
invoice(id: string): Promise<Invoice> {
return this.get<Invoice>(`/api/Invoices(guid'${id}')`);
}
payments(query: ODataQuery = {}): Promise<ODataCollection<Payment>> {
return this.get<ODataCollection<Payment>>(`/api/Payments${buildQueryString(query)}`);
}
products(query: ODataQuery = {}): Promise<ODataCollection<Product>> {
return this.get<ODataCollection<Product>>(`/api/Products${buildQueryString(query)}`);
}
activities(query: ODataQuery = {}): Promise<ODataCollection<Activity>> {
return this.get<ODataCollection<Activity>>(`/api/Activities${buildQueryString(query)}`);
}
/**
* Escritura controlada: dejado disponible para cuando el discovery
* confirme que es seguro. Por default el mode bloquea el método.
*/
addActivity(activity: Omit<Activity, "ID" | "CreatedAt">): Promise<Activity> {
return this.request<Activity>("POST", "/api/Activities", activity);
}
// --- Estado / observabilidad ------------------------------------------
stats(): { requestsToday: number; quota: number; remaining: number; mode: ClientMode } {
this.rolloverIfNewDay();
return {
requestsToday: this.requestCount,
quota: DAILY_QUOTA,
remaining: Math.max(0, DAILY_QUOTA - this.requestCount),
mode: this.mode,
};
}
// --- Implementación HTTP ----------------------------------------------
private get<T>(path: string): Promise<T> {
return this.request<T>("GET", path);
}
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
if (MUTATING.has(method) && this.mode === "read-only") {
throw new BindReadOnlyViolation(method, path);
}
this.rolloverIfNewDay();
const url = `${this.baseUrl}${path}`;
const headers: Record<string, string> = {
Authorization: `Bearer ${this.apiKey}`,
Accept: "application/json",
};
if (this.subscriptionKey) headers["Ocp-Apim-Subscription-Key"] = this.subscriptionKey;
if (body !== undefined) headers["Content-Type"] = "application/json";
if (this.mode === "dry-run" && MUTATING.has(method)) {
this.logger.info("[bind][dry-run]", method, url, body ?? "");
return undefined as T;
}
let lastErr: unknown;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
this.requestCount++;
const res = await this.fetchImpl(url, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
});
if (res.ok) {
const text = await res.text();
return (text ? JSON.parse(text) : undefined) as T;
}
const errBody = await safeJson(res);
if (res.status === 429 || res.status >= 500) {
if (attempt < this.maxRetries) {
const waitMs = backoffMs(attempt, res.headers.get("Retry-After"));
this.logger.warn(
`[bind] ${res.status} en ${path}, retry ${attempt + 1}/${this.maxRetries} en ${waitMs}ms`,
);
await sleep(waitMs);
continue;
}
}
throw new BindApiError(res.status, url, errBody);
} catch (err) {
lastErr = err;
if (err instanceof BindApiError) throw err;
if (attempt >= this.maxRetries) break;
await sleep(backoffMs(attempt, null));
}
}
throw lastErr ?? new Error("request failed");
}
private rolloverIfNewDay() {
const now = currentDayBucket();
if (now !== this.dayBucket) {
this.dayBucket = now;
this.requestCount = 0;
}
}
}
function backoffMs(attempt: number, retryAfter: string | null): number {
if (retryAfter) {
const secs = Number(retryAfter);
if (Number.isFinite(secs)) return secs * 1000;
}
return Math.min(1000 * 2 ** attempt, 8000) + Math.floor(Math.random() * 250);
}
function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
async function safeJson(res: Response): Promise<unknown> {
try {
return await res.json();
} catch {
return null;
}
}
function currentDayBucket(): string {
return new Date().toISOString().slice(0, 10);
}
+63
View File
@@ -0,0 +1,63 @@
/**
* Helpers para construir queries OData que entiende el API de BIND.
*
* Sintaxis observable en la doc oficial:
* /api/Products?$filter=ID eq guid'bbe2cc0c-...'&$skip=0&$top=50&$orderby=Name asc
*
* Mantengo el builder muy chico — sólo lo que el MVP necesita.
*/
export type ODataFilter = string;
export interface ODataQuery {
filter?: ODataFilter;
top?: number;
skip?: number;
orderby?: string;
select?: string[];
count?: boolean;
}
export function buildQueryString(q: ODataQuery): string {
const parts: string[] = [];
if (q.filter) parts.push(`$filter=${encodeURIComponent(q.filter)}`);
if (typeof q.top === "number") parts.push(`$top=${q.top}`);
if (typeof q.skip === "number") parts.push(`$skip=${q.skip}`);
if (q.orderby) parts.push(`$orderby=${encodeURIComponent(q.orderby)}`);
if (q.select?.length) parts.push(`$select=${encodeURIComponent(q.select.join(","))}`);
if (q.count) parts.push(`$count=true`);
return parts.length ? `?${parts.join("&")}` : "";
}
/**
* Pequeño helper para escribir filtros legibles. No es un parser OData;
* solo escapa comillas simples y envuelve guids.
*
* Ejemplos:
* eq("ID", guid("bbe2...")) -> "ID eq guid'bbe2...'"
* eq("Status", "issued") -> "Status eq 'issued'"
* and(eq("Status","issued"), gt("Total", 1000))
*/
export const guid = (id: string): string => `guid'${id.replaceAll("'", "''")}'`;
export const str = (s: string): string => `'${s.replaceAll("'", "''")}'`;
export const eq = (field: string, value: string | number | boolean): string =>
`${field} eq ${formatValue(value)}`;
export const ne = (field: string, value: string | number | boolean): string =>
`${field} ne ${formatValue(value)}`;
export const gt = (field: string, value: string | number): string =>
`${field} gt ${formatValue(value)}`;
export const lt = (field: string, value: string | number): string =>
`${field} lt ${formatValue(value)}`;
export const ge = (field: string, value: string | number): string =>
`${field} ge ${formatValue(value)}`;
export const le = (field: string, value: string | number): string =>
`${field} le ${formatValue(value)}`;
export const and = (...parts: string[]): string => parts.join(" and ");
export const or = (...parts: string[]): string => `(${parts.join(" or ")})`;
function formatValue(v: string | number | boolean): string {
if (typeof v === "number" || typeof v === "boolean") return String(v);
// Si ya viene formateado como guid'...' o '...' (string OData), respétalo.
if (/^(guid'.*'|'.*')$/.test(v)) return v;
return str(v);
}
+102
View File
@@ -0,0 +1,102 @@
/**
* Tipos del dominio de BIND ERP, modelados a partir de la documentación pública
* y de la convención observable del API (OData-like sobre Azure API Management).
*
* Estos tipos son una aproximación: la documentación detallada vive detrás de
* login en developers.bind.com.mx. Cuando se obtenga el API key se deben
* reconciliar contra el schema real (especialmente nombres exactos de campos).
*/
export type Guid = string; // BIND identifica recursos como guid'...' en filtros OData.
export type IsoDate = string; // ISO 8601, ej. "2026-05-28T10:00:00Z"
export type Decimal = number; // En producción debe envolverse a decimal(18,4) en Balam.
export type Currency = "MXN" | "USD" | "EUR";
export interface Customer {
ID: Guid;
Code: string;
Name: string;
TaxId: string; // RFC en MX, TaxID/EIN en US.
Country: string;
Email: string | null;
Currency: Currency;
PaymentTerms: number | null; // Días de crédito.
IsActive: boolean;
CreatedAt: IsoDate;
UpdatedAt: IsoDate;
}
export interface Product {
ID: Guid;
Code: string;
Name: string;
UnitPrice: Decimal;
Currency: Currency;
SatCode: string | null; // Catálogo SAT (ClaveProdServ).
IsActive: boolean;
}
export type InvoiceStatus = "draft" | "issued" | "paid" | "partial" | "overdue" | "cancelled";
export interface InvoiceLine {
ProductID: Guid;
Description: string;
Quantity: Decimal;
UnitPrice: Decimal;
TaxRate: Decimal; // ej. 0.16 = IVA 16 %
Subtotal: Decimal;
Total: Decimal;
}
export interface Invoice {
ID: Guid;
Folio: string;
Serie: string;
UUID: string | null; // UUID del CFDI cuando ya fue timbrada por el PAC integrado de BIND.
CustomerID: Guid;
IssueDate: IsoDate;
DueDate: IsoDate;
Currency: Currency;
ExchangeRate: Decimal | null; // TC al momento de emisión (relevante para USD/EUR).
Subtotal: Decimal;
Taxes: Decimal;
Total: Decimal;
Balance: Decimal; // Saldo pendiente.
Status: InvoiceStatus;
Lines: InvoiceLine[];
XmlUrl: string | null;
PdfUrl: string | null;
}
export interface Payment {
ID: Guid;
InvoiceID: Guid;
PaymentDate: IsoDate;
Amount: Decimal;
Currency: Currency;
Method: "cash" | "transfer" | "card" | "check" | "other";
Reference: string | null;
}
export interface Activity {
ID: Guid;
CustomerID: Guid | null;
InvoiceID: Guid | null;
Type: string;
Subject: string;
Notes: string | null;
CreatedAt: IsoDate;
CreatedBy: string;
}
/**
* Respuesta paginada estilo OData v3/v4: la API responde con
* { value: [...], "odata.count"?: number, "odata.nextLink"?: string }.
* Modelamos solo lo que necesita el cliente.
*/
export interface ODataCollection<T> {
value: T[];
count?: number;
nextLink?: string;
}
+143
View File
@@ -0,0 +1,143 @@
/**
* Demo end-to-end: cinco escenarios que tocan los casos críticos del MVP
* de Balam (BIND-first) tal como salieron en el discovery.
*
* 1. Listar clientes activos -> base del dashboard
* 2. Filtrar facturas vencidas (cobranza) -> motor de recordatorios
* 3. Calcular CxC por cliente -> KPI directivo
* 4. Detectar factura USD a cliente extranjero -> regla sin IVA + TC
* 5. Intentar una escritura en modo read-only -> demuestra el guardrail
*
* Por default apunta al mock local. Para correrlo contra producción:
* BIND_BASE_URL=https://api.bind.com.mx \
* BIND_API_KEY=<perfil-usuario/integraciones> \
* BIND_SUBSCRIPTION_KEY=<si-aplica> \
* pnpm demo
*/
import { BindClient, BindReadOnlyViolation } from "./client/BindClient.js";
import { and, eq, ge, guid, lt } from "./client/odata.js";
const cfg = {
baseUrl: process.env.BIND_BASE_URL ?? "http://localhost:4010",
apiKey: process.env.BIND_API_KEY ?? "mock-bearer-token",
subscriptionKey: process.env.BIND_SUBSCRIPTION_KEY,
mode: (process.env.BIND_MODE as "read-only" | "dry-run" | "write") ?? "read-only",
};
const client = new BindClient(cfg);
async function main() {
banner(`BIND API sandbox — modo: ${cfg.mode} · base: ${cfg.baseUrl}`);
// ── 1. Clientes activos ───────────────────────────────────────────────
step("1. Listar clientes activos (sería el seed del dashboard)");
const active = await client.customers({
filter: eq("IsActive", true),
orderby: "Name asc",
top: 50,
count: true,
});
console.table(
active.value.map((c) => ({
Code: c.Code,
Name: c.Name,
Currency: c.Currency,
Country: c.Country,
Terms: c.PaymentTerms,
})),
);
// ── 2. Facturas vencidas ──────────────────────────────────────────────
step("2. Facturas vencidas — motor de cobranza");
const today = new Date().toISOString();
const overdue = await client.invoices({
filter: and(eq("Status", "overdue"), lt("DueDate", `'${today}'`)),
orderby: "DueDate asc",
});
console.table(
overdue.value.map((i) => ({
Folio: `${i.Serie}-${i.Folio}`,
Cliente: shortId(i.CustomerID),
DueDate: i.DueDate.slice(0, 10),
Currency: i.Currency,
Balance: i.Balance,
})),
);
// ── 3. Aging por cliente (cuentas por cobrar) ─────────────────────────
step("3. CxC por cliente (sólo MXN para simplificar el demo)");
const open = await client.invoices({
filter: and(eq("Currency", "MXN"), ge("Balance", 0.01)),
});
const byCustomer = new Map<string, number>();
for (const inv of open.value) {
byCustomer.set(inv.CustomerID, (byCustomer.get(inv.CustomerID) ?? 0) + inv.Balance);
}
const customersIndex = new Map(
(await client.customers({ top: 200 })).value.map((c) => [c.ID, c.Name]),
);
console.table(
[...byCustomer.entries()].map(([id, total]) => ({
Cliente: customersIndex.get(id) ?? id,
CxC_MXN: total.toFixed(2),
})),
);
// ── 4. Facturas USD a cliente extranjero ──────────────────────────────
step("4. Facturas USD — validar regla sin-IVA + tipo de cambio fijado");
const usd = await client.invoices({ filter: eq("Currency", "USD") });
for (const inv of usd.value) {
const customer = await client.customer(inv.CustomerID);
console.log(
` ${inv.Serie}-${inv.Folio} cliente=${customer.Name} (${customer.Country}) total=$${inv.Total} USD TC=${inv.ExchangeRate ?? "—"} IVA=${inv.Taxes}`,
);
}
// ── 5. Guardrail de escritura ─────────────────────────────────────────
step("5. Intento de escritura en read-only (debe BLOQUEARSE)");
try {
await client.addActivity({
CustomerID: active.value[0]!.ID,
InvoiceID: null,
Type: "note",
Subject: "Prueba desde sandbox",
Notes: null,
CreatedBy: "demo",
});
console.log(" ⚠️ La escritura PASÓ — revisar BIND_MODE.");
} catch (err) {
if (err instanceof BindReadOnlyViolation) {
console.log(` ✅ Guardrail OK: ${err.message}`);
} else {
throw err;
}
}
// ── Resumen ───────────────────────────────────────────────────────────
banner("Stats del cliente");
console.log(client.stats());
// Caso opcional: si se pasa --invoice <guid> bajamos el documento.
const wantInvoice = process.argv.find((a) => a.startsWith("--invoice="));
if (wantInvoice) {
const id = wantInvoice.split("=")[1]!;
step(`Lookup directo: /api/Invoices(${guid(id)})`);
console.log(await client.invoice(id));
}
}
function banner(s: string) {
console.log(`\n${"═".repeat(72)}\n${s}\n${"═".repeat(72)}`);
}
function step(s: string) {
console.log(`\n── ${s}`);
}
function shortId(id: string): string {
return id.slice(0, 8);
}
main().catch((err) => {
console.error("Demo falló:", err);
process.exit(1);
});
@@ -0,0 +1,261 @@
/**
* Datos sintéticos que imitan lo que BIND devolvería para una cuenta
* del tamaño de Balam (45 colaboradores + 5 freelancers, ~50 facturas/mes).
*
* Nada de esto es información real de Balam. Solo cumple con la *forma*
* del payload para que el cliente y los handlers se validen.
*/
import type {
Activity,
Customer,
Invoice,
InvoiceLine,
Payment,
Product,
} from "../../client/types.js";
export const customers: Customer[] = [
{
ID: "11111111-1111-1111-1111-111111111111",
Code: "ACU-001",
Name: "Acuntia México SA de CV",
TaxId: "ACU010203AB1",
Country: "MX",
Email: "facturacion@acuntia.example",
Currency: "MXN",
PaymentTerms: 30,
IsActive: true,
CreatedAt: "2024-01-15T10:00:00Z",
UpdatedAt: "2026-04-01T10:00:00Z",
},
{
ID: "22222222-2222-2222-2222-222222222222",
Code: "CLI-002",
Name: "TechMex Innovaciones SAPI",
TaxId: "TMI150301CD2",
Country: "MX",
Email: "ap@techmex.example",
Currency: "MXN",
PaymentTerms: 45,
IsActive: true,
CreatedAt: "2024-03-20T10:00:00Z",
UpdatedAt: "2026-04-15T10:00:00Z",
},
{
ID: "33333333-3333-3333-3333-333333333333",
Code: "CLI-003",
Name: "Norteamericana Logistics LLC",
TaxId: "98-7654321",
Country: "US",
Email: "billing@norteam.example",
Currency: "USD",
PaymentTerms: 60,
IsActive: true,
CreatedAt: "2025-02-10T10:00:00Z",
UpdatedAt: "2026-05-01T10:00:00Z",
},
{
ID: "44444444-4444-4444-4444-444444444444",
Code: "CLI-004",
Name: "Distribuidora del Golfo SA",
TaxId: "DGO180815EF3",
Country: "MX",
Email: "pagos@dgolfo.example",
Currency: "MXN",
PaymentTerms: 30,
IsActive: true,
CreatedAt: "2025-06-01T10:00:00Z",
UpdatedAt: "2026-05-10T10:00:00Z",
},
{
ID: "55555555-5555-5555-5555-555555555555",
Code: "CLI-005",
Name: "Servicios Estratégicos del Norte",
TaxId: "SEN200401GH4",
Country: "MX",
Email: "tesoreria@sen.example",
Currency: "MXN",
PaymentTerms: 15,
IsActive: false,
CreatedAt: "2025-08-12T10:00:00Z",
UpdatedAt: "2026-03-22T10:00:00Z",
},
];
export const products: Product[] = [
{
ID: "a1111111-1111-1111-1111-111111111111",
Code: "SVC-CONSULT",
Name: "Consultoría estratégica · hora",
UnitPrice: 1800,
Currency: "MXN",
SatCode: "80101504",
IsActive: true,
},
{
ID: "a2222222-2222-2222-2222-222222222222",
Code: "SVC-RECRUIT",
Name: "Búsqueda de talento ejecutivo",
UnitPrice: 45000,
Currency: "MXN",
SatCode: "80111501",
IsActive: true,
},
];
function line(product: Product, qty: number, taxRate: number): InvoiceLine {
const subtotal = round2(qty * product.UnitPrice);
const total = round2(subtotal * (1 + taxRate));
return {
ProductID: product.ID,
Description: product.Name,
Quantity: qty,
UnitPrice: product.UnitPrice,
TaxRate: taxRate,
Subtotal: subtotal,
Total: total,
};
}
export const invoices: Invoice[] = [
// 1) Pagada
{
ID: "f0000001-0000-0000-0000-000000000001",
Folio: "0001",
Serie: "A",
UUID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA0001",
CustomerID: customers[1]!.ID,
IssueDate: "2026-03-01T10:00:00Z",
DueDate: "2026-04-15T10:00:00Z",
Currency: "MXN",
ExchangeRate: null,
Subtotal: 90000,
Taxes: 14400,
Total: 104400,
Balance: 0,
Status: "paid",
Lines: [line(products[1]!, 2, 0.16)],
XmlUrl: "https://api.bind.com.mx/api/Invoices/f0000001/xml",
PdfUrl: "https://api.bind.com.mx/api/Invoices/f0000001/pdf",
},
// 2) Vigente
{
ID: "f0000002-0000-0000-0000-000000000002",
Folio: "0002",
Serie: "A",
UUID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA0002",
CustomerID: customers[3]!.ID,
IssueDate: "2026-05-10T10:00:00Z",
DueDate: "2026-06-09T10:00:00Z",
Currency: "MXN",
ExchangeRate: null,
Subtotal: 36000,
Taxes: 5760,
Total: 41760,
Balance: 41760,
Status: "issued",
Lines: [line(products[0]!, 20, 0.16)],
XmlUrl: null,
PdfUrl: null,
},
// 3) Vencida — caso cobranza
{
ID: "f0000003-0000-0000-0000-000000000003",
Folio: "0003",
Serie: "A",
UUID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA0003",
CustomerID: customers[1]!.ID,
IssueDate: "2026-02-15T10:00:00Z",
DueDate: "2026-04-01T10:00:00Z",
Currency: "MXN",
ExchangeRate: null,
Subtotal: 18000,
Taxes: 2880,
Total: 20880,
Balance: 20880,
Status: "overdue",
Lines: [line(products[0]!, 10, 0.16)],
XmlUrl: null,
PdfUrl: null,
},
// 4) USD a cliente Texas — sin IVA (exportación)
{
ID: "f0000004-0000-0000-0000-000000000004",
Folio: "0004",
Serie: "A",
UUID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA0004",
CustomerID: customers[2]!.ID,
IssueDate: "2026-05-20T10:00:00Z",
DueDate: "2026-07-19T10:00:00Z",
Currency: "USD",
ExchangeRate: 17.85,
Subtotal: 12500,
Taxes: 0,
Total: 12500,
Balance: 12500,
Status: "issued",
Lines: [
{
ProductID: products[1]!.ID,
Description: products[1]!.Name,
Quantity: 1,
UnitPrice: 12500,
TaxRate: 0,
Subtotal: 12500,
Total: 12500,
},
],
XmlUrl: null,
PdfUrl: null,
},
// 5) Cliente estratégico — ACUNTIA (no debe recibir recordatorio auto)
{
ID: "f0000005-0000-0000-0000-000000000005",
Folio: "0005",
Serie: "A",
UUID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA0005",
CustomerID: customers[0]!.ID,
IssueDate: "2026-04-20T10:00:00Z",
DueDate: "2026-05-20T10:00:00Z",
Currency: "MXN",
ExchangeRate: null,
Subtotal: 54000,
Taxes: 8640,
Total: 62640,
Balance: 62640,
Status: "overdue",
Lines: [line(products[0]!, 30, 0.16)],
XmlUrl: null,
PdfUrl: null,
},
];
export const payments: Payment[] = [
{
ID: "p0000001-0000-0000-0000-000000000001",
InvoiceID: invoices[0]!.ID,
PaymentDate: "2026-04-10T10:00:00Z",
Amount: 104400,
Currency: "MXN",
Method: "transfer",
Reference: "SPEI 7XX9-2026-04-10",
},
];
export const activities: Activity[] = [
{
ID: "ac000001-0000-0000-0000-000000000001",
CustomerID: customers[1]!.ID,
InvoiceID: invoices[2]!.ID,
Type: "reminder",
Subject: "Recordatorio enviado a TechMex (vencida 30 días)",
Notes: "Plantilla cobranza-vencida-30d",
CreatedAt: "2026-04-05T16:00:00Z",
CreatedBy: "balam-collections-bot",
},
];
function round2(n: number): number {
return Math.round(n * 100) / 100;
}
@@ -0,0 +1,187 @@
/**
* Mini-evaluador de filtros OData para el mock.
*
* NO es un parser completo de OData — soporta sólo lo que el cliente del MVP
* genera con los helpers de src/client/odata.ts:
*
* - `Field eq 'value'` / `Field eq guid'...'` / `Field eq 123`
* - `Field ne | gt | lt | ge | le ...`
* - cadenas con AND/OR y paréntesis simples
*
* Suficiente para validar end-to-end que el cliente arma URLs correctas.
*/
type Op = "eq" | "ne" | "gt" | "lt" | "ge" | "le";
type Value = string | number | boolean | null;
interface Comparison {
kind: "cmp";
field: string;
op: Op;
value: Value;
}
interface And {
kind: "and";
left: Node;
right: Node;
}
interface Or {
kind: "or";
left: Node;
right: Node;
}
type Node = Comparison | And | Or;
export function evalFilter<T extends Record<string, unknown>>(
filter: string | undefined,
row: T,
): boolean {
if (!filter) return true;
const node = parse(tokenize(filter));
return run(node, row);
}
// --- tokenizer ---------------------------------------------------------
type Token = { type: "ident" | "op" | "value" | "lparen" | "rparen" | "and" | "or"; v: string };
function tokenize(input: string): Token[] {
const tokens: Token[] = [];
let i = 0;
while (i < input.length) {
const c = input[i]!;
if (c === " ") {
i++;
continue;
}
if (c === "(") {
tokens.push({ type: "lparen", v: "(" });
i++;
continue;
}
if (c === ")") {
tokens.push({ type: "rparen", v: ")" });
i++;
continue;
}
if (c === "'") {
let j = i + 1;
let s = "";
while (j < input.length) {
if (input[j] === "'" && input[j + 1] === "'") {
s += "'";
j += 2;
} else if (input[j] === "'") {
break;
} else {
s += input[j];
j++;
}
}
tokens.push({ type: "value", v: s });
i = j + 1;
continue;
}
// guid'...'
if (input.startsWith("guid'", i)) {
const end = input.indexOf("'", i + 5);
tokens.push({ type: "value", v: input.slice(i + 5, end) });
i = end + 1;
continue;
}
// identifier / op / and / or / number / bool
const m = /^[A-Za-z_][A-Za-z0-9_]*|^-?\d+(\.\d+)?/.exec(input.slice(i));
if (!m) throw new Error(`No puedo tokenizar en pos ${i}: ${input.slice(i)}`);
const raw = m[0];
i += raw.length;
if (/^-?\d/.test(raw)) {
tokens.push({ type: "value", v: raw });
continue;
}
const lower = raw.toLowerCase();
if (lower === "and") tokens.push({ type: "and", v: "and" });
else if (lower === "or") tokens.push({ type: "or", v: "or" });
else if (lower === "true" || lower === "false") tokens.push({ type: "value", v: lower });
else if (["eq", "ne", "gt", "lt", "ge", "le"].includes(lower))
tokens.push({ type: "op", v: lower });
else tokens.push({ type: "ident", v: raw });
}
return tokens;
}
// --- parser (precedencia: paréntesis > and > or) -----------------------
function parse(tokens: Token[]): Node {
let pos = 0;
const peek = () => tokens[pos];
const eat = () => tokens[pos++]!;
function parseOr(): Node {
let left = parseAnd();
while (peek()?.type === "or") {
eat();
left = { kind: "or", left, right: parseAnd() };
}
return left;
}
function parseAnd(): Node {
let left = parseAtom();
while (peek()?.type === "and") {
eat();
left = { kind: "and", left, right: parseAtom() };
}
return left;
}
function parseAtom(): Node {
const t = eat();
if (t.type === "lparen") {
const inner = parseOr();
if (eat().type !== "rparen") throw new Error("Falta )");
return inner;
}
if (t.type !== "ident") throw new Error(`Esperaba identificador, recibí ${t.type}`);
const opTok = eat();
if (opTok.type !== "op") throw new Error(`Esperaba operador después de ${t.v}`);
const valTok = eat();
if (valTok.type !== "value") throw new Error(`Esperaba valor después de ${opTok.v}`);
return {
kind: "cmp",
field: t.v,
op: opTok.v as Op,
value: coerce(valTok.v),
};
}
return parseOr();
}
function coerce(raw: string): Value {
if (raw === "true") return true;
if (raw === "false") return false;
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
return raw;
}
function run(node: Node, row: Record<string, unknown>): boolean {
if (node.kind === "and") return run(node.left, row) && run(node.right, row);
if (node.kind === "or") return run(node.left, row) || run(node.right, row);
const left = row[node.field];
const right = node.value;
switch (node.op) {
case "eq":
return left == right;
case "ne":
return left != right;
case "gt":
return (left as number) > (right as number);
case "lt":
return (left as number) < (right as number);
case "ge":
return (left as number) >= (right as number);
case "le":
return (left as number) <= (right as number);
default: {
const _exhaustive: never = node.op;
return _exhaustive;
}
}
}
+167
View File
@@ -0,0 +1,167 @@
/**
* Mock server que imita api.bind.com.mx para desarrollo y pruebas.
*
* - Valida los headers que documenta BIND (Authorization: Bearer + opcionalmente
* Ocp-Apim-Subscription-Key).
* - Soporta $filter, $top, $skip, $orderby, $count sobre las colecciones de seed.
* - Devuelve respuestas en el formato OData v3-ish:
* { value: [...], "odata.count": N }
* - Inyecta latencia y 429 ocasional vía query (?simulate=throttle|slow) para
* ejercitar el cliente.
*
* Por qué un mock y no Postman/Wiremock: el contrato de BIND no es público en
* detalle, así que necesitamos un sandbox que evolucione con lo que vayamos
* descubriendo del API real sin pagar por una herramienta extra.
*/
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { URL } from "node:url";
import { activities, customers, invoices, payments, products } from "./data/seed.js";
import { evalFilter } from "./odata-filter.js";
const PORT = Number(process.env.MOCK_PORT ?? 4010);
interface Collection<T> {
rows: T[];
byIdField?: keyof T;
}
const COLLECTIONS: Record<string, Collection<any>> = {
Customers: { rows: customers, byIdField: "ID" },
Invoices: { rows: invoices, byIdField: "ID" },
Payments: { rows: payments, byIdField: "ID" },
Products: { rows: products, byIdField: "ID" },
Activities: { rows: activities, byIdField: "ID" },
};
const server = createServer(async (req, res) => {
try {
await handle(req, res);
} catch (err) {
sendJson(res, 500, { error: String((err as Error).message) });
}
});
async function handle(req: IncomingMessage, res: ServerResponse) {
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
if (url.pathname === "/health") return sendJson(res, 200, { ok: true });
// Validación de headers tipo BIND
const auth = req.headers["authorization"];
if (!auth || !String(auth).toLowerCase().startsWith("bearer ")) {
return sendJson(res, 401, {
error: { code: "Unauthorized", message: "Missing Authorization: Bearer <api-key>" },
});
}
// Subscription key es opcional según la doc, pero si la mandan validamos shape.
const sub = req.headers["ocp-apim-subscription-key"];
if (sub !== undefined && String(sub).length < 5) {
return sendJson(res, 401, {
error: { code: "Unauthorized", message: "Subscription key inválida" },
});
}
// Simulación de fallas para ejercitar el cliente
const simulate = url.searchParams.get("simulate");
if (simulate === "throttle") {
res.setHeader("Retry-After", "1");
return sendJson(res, 429, {
error: { code: "TooManyRequests", message: "Cuota diaria excedida (simulado)" },
});
}
if (simulate === "slow") {
await new Promise((r) => setTimeout(r, 1500));
}
// Rutas: /api/{Recurso} y /api/{Recurso}(guid'...')
const m = /^\/api\/([A-Za-z]+)(?:\(guid'([^']+)'\))?\/?$/.exec(url.pathname);
if (!m) return sendJson(res, 404, { error: { code: "NotFound", path: url.pathname } });
const [, resource, id] = m;
const col = COLLECTIONS[resource!];
if (!col) return sendJson(res, 404, { error: { code: "ResourceNotFound", resource } });
if (req.method === "GET" && id) {
const row = col.rows.find((r) => r[col.byIdField!] === id);
if (!row) return sendJson(res, 404, { error: { code: "NotFound", id } });
return sendJson(res, 200, row);
}
if (req.method === "GET") {
const $filter = url.searchParams.get("$filter") ?? undefined;
const $top = parseIntOr(url.searchParams.get("$top"), col.rows.length);
const $skip = parseIntOr(url.searchParams.get("$skip"), 0);
const $orderby = url.searchParams.get("$orderby") ?? undefined;
const $count = url.searchParams.get("$count") === "true";
let rows = col.rows.filter((r) => evalFilter($filter, r));
if ($orderby) rows = applyOrderBy(rows, $orderby);
const totalCount = rows.length;
rows = rows.slice($skip, $skip + $top);
const body: { value: unknown[]; "odata.count"?: number } = { value: rows };
if ($count) body["odata.count"] = totalCount;
return sendJson(res, 200, body);
}
if (req.method === "POST" && resource === "Activities" && !id) {
const payload = await readJson(req);
const created = {
ID: cryptoRandomGuid(),
CreatedAt: new Date().toISOString(),
...payload,
};
activities.push(created as any);
return sendJson(res, 201, created);
}
return sendJson(res, 405, { error: { code: "MethodNotAllowed", method: req.method } });
}
function parseIntOr(raw: string | null, fallback: number): number {
if (raw === null) return fallback;
const n = Number.parseInt(raw, 10);
return Number.isFinite(n) ? n : fallback;
}
function applyOrderBy<T extends Record<string, unknown>>(rows: T[], orderby: string): T[] {
const [field, dir] = orderby.trim().split(/\s+/);
const sign = dir?.toLowerCase() === "desc" ? -1 : 1;
return [...rows].sort((a, b) => {
const av = a[field!] as any;
const bv = b[field!] as any;
if (av < bv) return -1 * sign;
if (av > bv) return 1 * sign;
return 0;
});
}
async function readJson(req: IncomingMessage): Promise<any> {
const chunks: Buffer[] = [];
for await (const c of req) chunks.push(c as Buffer);
const raw = Buffer.concat(chunks).toString("utf8");
return raw ? JSON.parse(raw) : {};
}
function sendJson(res: ServerResponse, status: number, body: unknown): void {
res.statusCode = status;
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.end(JSON.stringify(body));
}
function cryptoRandomGuid(): string {
// Suficiente para mock. En prod BIND emite sus propios IDs.
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
server.listen(PORT, () => {
console.log(`[bind-mock] escuchando en http://localhost:${PORT}`);
console.log("[bind-mock] recursos: /api/Customers /api/Invoices /api/Payments /api/Products /api/Activities");
console.log("[bind-mock] auth requerida: Authorization: Bearer <cualquier-cosa>");
});
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2023"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"isolatedModules": true,
"verbatimModuleSyntax": false,
"types": ["node"],
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
+47
View File
@@ -0,0 +1,47 @@
# PENDIENTES — acciones abiertas
Acciones vivas del proyecto. Formato: `[ ]` abierta · `[x]` cerrada (no se borran, dejan rastro). Cada una con responsable y, si aplica, fecha. Ver contexto en [REGISTRO.md](REGISTRO.md).
_Última actualización: 2026-06-30 (plan de actividades entregado; kickoff con Noé el 1-jul, 7am)._
## 🔴 Johann (proveedor) — inmediato
- [ ] **Asistir al kickoff con Noé (mié 1-jul, 7:00 am)** — llevar agenda, lista de accesos a pedir y preguntas de Discovery. Ver [REGISTRO #21](REGISTRO.md).
- [x] ~~Preparar el Excel de actividades~~**Entregado:** Etapa 0 y 1 (29-jun) y **completo, 4 etapas (03)** con fechas tentativas (30-jun). En `../planeacion/Plan-actividades.xlsx`.
- [x] ~~Proponer sesiones de Discovery~~**Hecho (29-jun):** propuestas y aceptadas; **Erika coordina las agendas** (intermediaria de sesiones).
- [ ] Cambiar régimen fiscal (en proceso) para poder facturar (CFDI semanal los viernes, pago a 30 días).
- [ ] Confirmar **qué permisos exactos de Azure** necesita (crear App Service + PostgreSQL; no Global Admin).
- [ ] **Arrancar el Discovery** una vez Balam entregue los accesos (el contrato ya está firmado).
- [ ] (Opcional) Pedir a Balam **copia limpia del contrato**: la cláusula de Firma Electrónica de la última página quedó duplicada y aún dice "EL PATRÓN" (residuo de plantilla, bajo riesgo). Ver [REGISTRO #19](REGISTRO.md).
- [x] ~~Revisar y firmar el contrato de servicios~~**Hecho (26-jun):** revisado, negociados 2 ajustes (pago de horas al terminar + aceptación a 10 días) y **FIRMADO**. Ver [REGISTRO #18](REGISTRO.md), [#19](REGISTRO.md).
- [x] ~~Responder el correo de Noe (8-jun)~~**Hecho (10-jun):** aceptados los 4 ajustes y respondidas las 3 preguntas técnicas. Ver [REGISTRO #16](REGISTRO.md).
- [x] ~~Decisión comercial: sin anticipo / pago a 30 días~~**Aceptado (10-jun)**, con la condición de **firmar el contrato/orden de trabajo antes de arrancar** (sustituye al anticipo como mecanismo de compromiso).
- [x] ~~Preparar propuesta v1.1~~**Enviada (10-jun)** con los 4 ajustes reflejados. Ver [REGISTRO #16](REGISTRO.md):
- [x] **Soporte:** bolsa de horas a $600/h, vigencia 12 meses (sin caducidad mensual).
- [x] **Garantía:** 45 días.
- [x] **Comercial:** sin anticipo; factura de Etapa 0 + pago a 30 días; avances a 30 días.
- [x] **Conciliación bancaria:** reflejada como primer alcance condicionado a horas liberadas por el Discovery.
- [x] **Dashboard/reporteo:** ajustado para no solapar con el tablero Power BI de Pedro (conservar cobranza + alertas).
## 🟡 Balam
- [x] ~~**Balam:** enviar el documento/contrato de firma~~**Hecho (25-jun):** contrato enviado vía Paola (RH); ajustado y **firmado el 26-jun**. Ver [REGISTRO #18](REGISTRO.md), [#19](REGISTRO.md).
- [ ] 🔑 **Balam: entregar los ACCESOS para arrancar** — API BIND (cuenta ARA / llave de Arturo), Azure (Guajardo/Erika), manual de marca (Pedro), reglas de negocio + bancos (Arturo). **Es el bloqueador para iniciar el Discovery.**
- [x] ~~Erika (PM): pedir el plan de actividades~~**Recibido (30-jun).** Erika es la **intermediaria de todas las sesiones**, agendó el **kickoff (1-jul, 7am)** y monta el **tablero Kanban en Jira**. Ver [REGISTRO #21](REGISTRO.md).
- [x] ~~**Noe:** formalizar por correo~~**Hecho:** aclaraciones (8-jun, [#15](REGISTRO.md)) y **luz verde + redacción del documento de firma** (16-jun, [#17](REGISTRO.md)).
- [ ] **Pedro + Erika:** armar el **tablero de seguimiento en Jira** y revisarlo juntos (instruido formalmente por Noe el 16-jun).
- [ ] **Pedro:** enviar **manual de marca** (paleta, tipografía, logos).
- [ ] **Pedro:** revisar API BIND — ¿cuántas llaves por usuario? Generar la de **desarrollo desde la cuenta maestra (ARA)** con permisos de Arturo; documentar cuál es para qué (no confundir con la del Power BI).
- [ ] **Erika / Guajardo:** gestionar la **cuenta/permiso de Azure**.
- [ ] **Arturo:** definir **reglas de negocio** + dar acceso/contexto de **bancos** (para conciliación).
## ⚙️ Acordado (referencia, ya cerrado)
- [x] ~~Anticipo de Discovery ($18,000): aprobado (4-jun)~~**Corregido (8-jun): Balam NO maneja anticipos.** Se factura la Etapa 0 (30 h) y se paga a 30 días.
- [x] **Propuesta v1.1 enviada (10-jun) y aceptada por Balam (16-jun).** Balam redacta el documento de firma.
- [x] **Condición de arranque: contrato/orden de trabajo firmado antes de iniciar** (planteado por Johann 10-jun; aceptado por Balam 16-jun).
- [x] **Contrato de servicios FIRMADO (26-jun-2026)** por Johann (firma electrónica). Incluye 2 ajustes finales: pago de horas al terminar + aceptación a 10 días naturales. **Facturación semanal (viernes)**, pago a 30 días.
- [x] **Plan de actividades (4 etapas) entregado** a Erika (2930 jun). **Kickoff con Noé agendado (1-jul, 7am).** Erika = intermediaria de sesiones; tablero Kanban en Jira.
- [x] Pagos: **a 30 días** post-factura (incluida la Etapa 0); política firme de Balam (8-jun).
- [x] Gestión en Jira (ágil, no Gantt); Erika valida entregables (4-jun).
- [x] Comunicación: canal de WhatsApp + correo para evidencia. Contactos: Erika=principal, Pedro=técnico, Arturo=negocio (4-jun).
+89
View File
@@ -0,0 +1,89 @@
# Bitácora — cómo registrar
Esta carpeta es el **registro vivo** del proyecto Balam. Cada vez que pase algo relevante —una llamada, un correo, un mensaje de WhatsApp, una decisión— se anota aquí. Así el proyecto siempre tiene una sola fuente de la verdad de "qué se dijo, cuándo y qué quedó pendiente".
## Archivos
| Archivo | Para qué |
|---|---|
| [REGISTRO.md](REGISTRO.md) | **Log cronológico** de toda comunicación (correos, llamadas, mensajes). Entradas numeradas, más antigua arriba. |
| [PENDIENTES.md](PENDIENTES.md) | **Acciones abiertas** (checklist). Lo que hay que hacer y quién. |
| `../fuentes/` | **Material crudo**: transcripciones, archivos `.eml`, PRD. No se edita; es evidencia. |
| `../README.md` | **Estado del proyecto** (resumen ejecutivo, datos clave, decisiones). Se actualiza cuando algo cambia el rumbo. |
## Flujo cuando pasa algo
1. **Pasa algo** (llamada / correo / mensaje / decisión).
2. **Guarda la evidencia** en `../fuentes/` si existe (transcripción, `.eml`, captura). Nómbrala con fecha: `2026-06-04 - Transcript - <tema>.txt`.
3. **Registra la entrada** en [REGISTRO.md](REGISTRO.md) con la plantilla que corresponda (ver abajo). Usa el siguiente número consecutivo.
4. **Mueve los pendientes** que surjan a [PENDIENTES.md](PENDIENTES.md).
5. Si cambió el **alcance, precio, plazo o una decisión clave**, actualiza también `../README.md`.
> Regla de oro: si no está en la bitácora, no pasó. Registrar toma 2 minutos y evita malentendidos caros.
---
## Plantillas
Copia el bloque, pégalo en REGISTRO.md y rellena. La fecha en formato `AAAA-MM-DD`.
### 📞 Llamada / reunión
```markdown
## N · AAAA-MM-DD HH:MM · Llamada · <tema>
> Participan: <nombres>. Canal: Teams / tel / presencial. Evidencia: `../fuentes/<archivo>` (si hay).
**Resumen:** <2-4 líneas de qué se trató.>
**Acuerdos / decisiones:**
- <decisión 1>
- <decisión 2>
**Pendientes que surgieron:**
- [ ] <responsable> — <acción> — <fecha objetivo>
**Citas relevantes:** *"<frase textual importante>"*
```
### ✉️ Correo
```markdown
## N · AAAA-MM-DD HH:MM · Correo · <De> → <Para> · <asunto>
> Dirección: enviado / recibido. Evidencia: `../fuentes/<archivo>.eml` (si se guardó).
**Resumen:** <qué dice.>
**Acción derivada:** <qué hay que hacer, si aplica.>
**Adjuntos:** <nombre del adjunto, si hay.>
```
### 💬 Mensaje (WhatsApp / Teams chat)
```markdown
## N · AAAA-MM-DD · Mensaje (WhatsApp) · <De> → <Para>
**Resumen:** <qué se dijo.>
**Acción:** <si aplica.>
```
### ⚖️ Decisión
```markdown
## N · AAAA-MM-DD · Decisión · <título>
**Contexto:** <por qué se decide.>
**Decisión:** <qué se decidió.>
**Aprobada por:** <quién.>
**Impacto:** alcance / precio / tiempo — <detalle.>
```
---
## Convenciones
- **Una entrada por evento**, numeradas y en orden cronológico (más reciente al final, igual que el hilo actual).
- **Evidencia cruda en `../fuentes/`**, nunca editada; el resumen interpretado va en REGISTRO.md.
- **Pendientes** siempre con responsable y fecha; al cerrarse se marcan `[x]` en PENDIENTES.md (no se borran, dejan rastro).
- **Fechas absolutas** (`2026-06-04`), nunca "ayer" o "la próxima semana".
- Citas textuales entre comillas y en *cursiva* para distinguir lo dicho de la interpretación.
+443
View File
@@ -0,0 +1,443 @@
# REGISTRO · Bitácora de comunicaciones — Proyecto Balam
> Log cronológico de **toda** comunicación del proyecto (correos, llamadas, mensajes). Es la fuente de la verdad del "qué se dijo y cuándo". Convención y plantillas para registrar en [README.md](README.md). Material crudo (transcripciones, .eml, PRD) en [`../fuentes/`](../fuentes/).
**Hilo de correo principal:** "Re: Solicitud de cotización PRD"
**Participantes:**
- **Johann** (proveedor) — `johann_antonio85@hotmail.com`
- **Noe Rocha** — CTO, Balam — `noe.rocha@balamtalentoestrategico.com`
- **Araceli Sanchez Jimenez** ("Ara") — Balam — `araceli.sanchez@balamtalentoestrategico.com`
- **Erika Chavez** — Project Manager, Balam — `erika.chavez@balamtalentoestrategico.com` · WhatsApp +52 1 81 2353 5803
- **Pedro Alberto Ayala Elizondo** — Desarrollador / contacto técnico, Balam — `pedro.ayala@balamtalentoestrategico.com`
- **Paola** — Recursos Humanos, Balam — coordinó la firma del contrato (WhatsApp)
**Periodo:** 30 abr 2026 → 30 jun 2026
**Orden:** cronológico (más antiguo arriba)
---
## 1 · Apr 30, 2026 — 4:38 PM · Erika Chavez → Johann
> **Asunto:** Solicitud de cotización PRD
> **CC:** Noe Rocha, Araceli Sanchez
Buenas tardes,
Espero que se encuentren muy bien. Mi nombre es Erika Chávez, Project Manager en Balam. Es un gusto saludarte, Johann.
Por medio de este medio, te comparto el PRD con la finalidad de solicitar tu apoyo en la elaboración de la cotización correspondiente.
Quedo atenta a cualquier duda o comentario.
Saludos.
---
## 2 · May 4, 2026 — 3:30 PM · Johann → Erika (CC Noe, Ara)
Buenos días Araceli,
Gracias por compartir el PRD, ya lo revisé a detalle durante el fin de semana. El alcance está muy claro y bien estructurado.
Para preparar la propuesta técnica con una estimación precisa por fase, me surgen las siguientes preguntas:
### Integraciones
- ¿BIND ERP tiene API habilitada o se trabaja con exportación de archivos?
- ¿BUK expone API para consultar nómina procesada?
- ¿Los 3 bancos entregan estados de cuenta por API, archivos (CSV/OFX) o PDFs?
- ¿Cuál es el banco americano que manejan?
- ¿Pueden dar acceso a ambientes sandbox de BIND y BUK para desarrollo, o solo cuentan con producción?
### Facturación
- ¿Ya cuentan con un PAC contratado para timbrado CFDI? ¿Cuál?
- La facturación a clientes en Texas, ¿es invoice estándar o requiere tax compliance específico?
- El PRD menciona soporte para EUR en RF-02, pero la regla de facturación internacional refiere solo a USD. ¿Confirman que el MVP debe incluir EUR, o lo dejamos preparado para una fase posterior?
### Cobranza
- El PRD indica que ACUNTIA y los "top 3 clientes" no deben recibir recordatorios automáticos. ¿Quiénes son esos top 3? ¿Es una lista fija o configurable?
- Se menciona el uso de tickets físicos en pagos con tarjeta como problema actual. ¿Esto se refiere a pagos con terminal donde el registro es manual?
### Datos y sistemas
- ¿"Book" es un sistema interno? ¿Cuenta con API o base de datos accesible?
- ¿Jira se usa únicamente para gestión de proyectos o también como fuente de datos de clientes/colaboradores?
### Operación
- ¿Aproximadamente cuántos colaboradores tienen en nómina y cuántas facturas emiten al mes?
- ¿Quién sería el contacto operativo para validar reglas de negocio durante el desarrollo?
### Infraestructura y diseño
- ¿Tienen servidor o nube preferida para hospedar la plataforma, o propongo opciones?
- ¿Cuentan con guía de marca / assets (logo, colores, tipografía) para la plataforma, o se diseña desde cero?
Con estas respuestas puedo tener la propuesta lista en 2448 horas. Si prefieren, podemos agendar una sesión corta de 2030 minutos para revisarlas en conjunto.
---
## 3 · May 4, 2026 — 7:59 PM · Noe Rocha → Johann (respuestas inline)
> Hola Johan, te paso los comentarios más abajo de lo que sé; algunos los tengo que investigar y otros los puede responder o aclarar Ara (TBD Ara).
| Pregunta | Respuesta de Noe |
|---|---|
| ¿BIND ERP tiene API habilitada? | **Exportación de archivos** |
| ¿BUK expone API? | **No hasta donde sabemos** |
| ¿Estados de cuenta de los 3 bancos? | **PDFs** |
| ¿Cuál es el banco americano? | TBD Ara |
| ¿Sandbox de BIND y BUK? | **Solo producción** |
| ¿PAC contratado para CFDI? | **Integrado en el mismo sistema de BIND** (hasta donde sabe) |
| ¿Facturación a Texas, tax compliance? | TBD Ara |
| ¿EUR en MVP? | **Dejarlo preparado para fase posterior. Por el momento solo USD** con empresas europeas |
| ¿Quiénes son ACUNTIA + top 3, lista fija o configurable? | **Lista configurable de acuerdo al objetivo de negocio** |
| ¿Tickets físicos en pagos con tarjeta? | TBD Ara |
| ¿"Book" es interno?, ¿API? | **Es un SaaS** |
| ¿Jira para qué se usa? | **Gestión de proyectos y servicios solamente con clientes** |
| ¿Cuántos colaboradores y facturas/mes? | **45 colaboradores + 5 proveedores tipo freelancer** |
| ¿Contacto operativo durante desarrollo? | **Un servidor (Noe) y el gerente administrativo** |
| ¿Nube preferida? | **Preferentemente Azure**; si hay mejor costo-beneficio, abiertos a evaluar |
| ¿Guía de marca / assets? | TBD Ara |
---
## 3a · May 6, 2026 — Araceli Sánchez → Johann · respuestas "en verde"
> **Asunto:** Re: Solicitud de cotización PRD
> *(Fuente: lista del hilo de Outlook. Su contenido íntegro no consta en el `.eml` disponible — ver aviso.)*
*"Te respondo en verde. Gracias, Saludos."* Araceli responde los puntos que Noe dejó marcados **TBD Ara** el 4-may: **(1)** banco americano, **(2)** tax compliance para clientes en Texas, **(3)** tickets físicos en pagos con tarjeta, **(4)** guía de marca / assets.
> ⚠️ **Evidencia pendiente:** el texto exacto de las respuestas "en verde" **no está en el `.eml` disponible** (capturado el 3-jun, donde esos 4 puntos aún figuran resaltados en amarillo como "TBD Ara", sin responder). **Acción:** recuperar el correo verde original para transcribir las respuestas aquí. El Resumen ejecutivo infiere banco = **IBC Bank Texas** y tax = **estándar** a partir de la llamada del 19-may; falta confirmar la atribución a este correo.
---
## 3b · May 6, 2026 — Johann → Balam · acuse
> **Asunto:** Re: Solicitud de cotización PRD
> *(Fuente: lista del hilo de Outlook.)*
*"Muchas gracias por sus comentarios. Los incorporo a la propuesta y se las comparto a la brevedad."* Johann acusa recibo de las respuestas (Noe inline + Araceli en verde) y anuncia que las incorpora a la propuesta.
---
## 4 · May 8, 2026 — 5:06 PM · Johann → Noe
Hola Noe,
Antes de compartirles la propuesta técnica final, me gustaría agendar una sesión contigo para revisar algunos puntos que impactan directamente el alcance:
1. **Dashboards:** ¿Qué campos e indicadores necesitan visualizar por módulo (facturación, cobranza, conciliación)? Quiero asegurarme de que el diseño refleje exactamente lo que el equipo de finanzas y dirección necesita ver.
2. **Mapa técnico de datos:** Sería muy útil que en la llamada me pudieras mostrar los sistemas desde donde sacaremos la información — qué sale de BUK, qué está en BIND, qué está en Book y cómo interactúan hoy. Esto me ayudará a dimensionar mejor la parte de integración y asegurar que no haya sorpresas durante el desarrollo.
3. **Acceso a bancos:** Si en algún punto se busca integración vía API bancaria, conviene definirlo desde ahora para contemplarlo en el alcance desde el inicio.
¿Tienes disponibilidad para una sesión de 3045 minutos?
---
## 5 · May 11, 2026 — 5:47 PM · Noe → Johann
Hola Johan,
Te paso opciones para una reunión:
- Miércoles de 7am a 8am, 6pm a 7pm
- Jueves de 7am a 8am
Te doy opciones muy temprano o muy tarde para que se te acomode en tu horario. Si hay tiempo para ti de verlo en horarios intermedios nos podemos adecuar.
---
## 6 · May 12, 2026 — 10:27 PM · Johann → Noe
Hola Noe,
Jueves de 7-8am me parece bien.
Saludos.
---
## 7 · May 16, 2026 — 10:32 AM · Noe → Johann
Buenos días Johan,
¿Podemos tener la sesión este martes? O bien, si es algo de una llamada, ¿a qué número te podría marcar?
> *(Implícita: la llamada se sostuvo el martes 19 de mayo de 2026 — transcripción en `../fuentes/2026-05-19 - Transcript - Solicitud de cotizacion.txt`.)*
---
## 7a · May 16, 2026 — Johann → Noe · confirma sesión + comparte teléfono
> **Asunto:** Re: Solicitud de cotización PRD
> *(Fuente: lista del hilo de Outlook.)*
*"Buen día Noe, claro, este martes podemos tener sesión. Cualquier cosa te comparto mi número: 8131610998. Saludos."* (La sesión se sostuvo el martes 19-may; ver #7.)
---
## 8 · May 25, 2026 — 10:41 PM · Noe → Johann
Hola Johann,
En el caso de **Bind tiene una API** que veo factible que podamos usar para nuestra necesidad:
- [Referencia API · Portal de desarrolladores · Bind ERP](https://developers.bind.com.mx/api-details#api=bind-erp-api&operation=Activities_AddActivity)
- [API de Bind ERP — sitio de ayuda](https://ayuda.bind.com.mx/hc/es/articles/360007437754-api-de-bind-erp)
Como dato tiene un **límite de 20 mil solicitudes diarias**, no sé si es poco o mucho para nuestra necesidad, pero hay que tomarlo en cuenta.
Como nuestro **primer interés es el tema de facturación**, considero que la propuesta sería **primero solo considerando el sistema ERP Bind**.
Saludos.
---
## 9 · May 27, 2026 — AM · Pedro (desarrollador, Balam) → Johann ⭐ (último mensaje)
Buenos días Johan,
Espero te encuentres bien. Quiero compartirte una **actualización respecto a nuestra plataforma BUK**: confirmamos que sí cuenta con **soporte API para integraciones con herramientas externas**. Puedes consultar la documentación disponible en el siguiente enlace.
> *(Nota: el link al API no se incluyó en el mensaje recibido; pendiente solicitarlo en la respuesta.)*
Aunque **esta integración no es prioridad en este momento**, queremos que cuentes con esta información para que puedas preparar una propuesta cuando sea oportuno avanzar con el tema.
En cuanto tengamos mayor claridad sobre los tiempos, te los haremos saber.
¡Saludos!
> **Lectura estratégica:**
> - Pedro está ejecutando lo que Noe le pidió en la llamada del 19-may (acelerar con BUK / Book / proveedores).
> - **No cambia el alcance del MVP** — BUK sigue siendo Fase 2 post-MVP.
> - **Sí confirma viabilidad técnica** del ciclo end-to-end Jira → BUK → Plataforma → BIND para Fase 2.
> - Pedro perfila como **contacto técnico día a día** durante el proyecto (responde la pregunta abierta del checklist).
---
## 10 · May 29, 2026 — 2:44 PM · Johann → Noe (CC Ara, Erika, Pedro) · envío de la propuesta
> **Asunto:** Re: Solicitud de cotización PRD
> Evidencia: hilo `../fuentes/RE_ Solicitud de cotización PRD (1).eml` (en este punto Pedro ya aparece en CC). Adjunta la **propuesta v1.0** en PDF. El cuerpo del correo ("Adjunto la propuesta para la plataforma de automatización financiera que conversamos…") consta dentro del `.eml`.
Envío de la **propuesta v1.0 en PDF** (`Propuesta-Balam.pdf`). MVP BIND-first enfocado en facturación: consulta + **emisión asistida** de facturas MXN/USD vía API de BIND (con dry-run + confirmación humana; timbra el PAC de BIND), cobranza operativa (aging, alertas internas, lista blanca ACUNTIA + Top 3), dashboard, reportes y bitácora. Inversión **$67,200 $81,600 MXN + IVA**, **67 semanas**, anticipo de 30 h (**$18,000 + IVA**) para Discovery. Módulos diferidos (conciliación, Stripe, recordatorios a clientes, asientos, BUK) cotizados de forma indicativa en el **Anexo B**.
---
## 11 · Jun 2, 2026 — 5:33 PM · Pedro → Johann, Noe (CC Ara, Erika)
> **Asunto:** RE: Solicitud de cotización PRD
"Buenas tardes, Johan. Gracias por compartir la propuesta. @Noe Rocha y yo la hemos revisado y tenemos **varios comentarios** que nos gustaría compartir contigo directamente en una **sesión conjunta**. ¿Nos podrías compartir tu disponibilidad para mañana por la tarde, o tu horario de preferencia para el jueves 4 de junio?"
---
## 12 · Jun 2, 2026 — 9:39 PM · Johann → Pedro, Noe (CC Ara, Erika)
> *"Buen día, Pedro: Claro que sí, con gusto. Me funcionaría mejor el jueves 4 de junio a las 7:00 am para revisar los comentarios juntos. Si a esa hora no l[es funciona]…"* (vista previa)
---
## 13 · Jun 3, 2026 · Pedro → Johann, Noe (confirmación + liga Teams)
> *"Buenos días, Johan. Te agendo para el día de mañana a las 7 am. Te comparto el link de la sesión. Reunión de Microsoft Teams — Unirse: https://teams.microsoft.com/meet/22…"* (vista previa)
Queda confirmada la sesión: **jueves 4-jun, 7:00 am, por Microsoft Teams**.
---
## 14 · Jun 4, 2026 — 7:00 AM · Llamada de revisión de propuesta ⭐
> Participan: Noe, Pedro, Erika y Johann. Transcripción completa en `../fuentes/2026-06-04 - Transcript - Revision de propuesta.txt`.
**Veredicto general:** *"En términos generales, la propuesta está bien."* El proyecto **avanza**; hay puntos a negociar/ajustar antes de formalizar.
**Acuerdos y decisiones:**
1. **Anticipo $18,000 (Discovery): APROBADO.** Arrancan con eso; el monto final del MVP se aclara tras Discovery (Noe entiende que el rango $67.2K$81.6K depende de lo que se encuentre).
2. **Conciliación: Noe la quiere incluir** (hoy es 100% manual y ya es un problema real). Vio que está cotizada en el Anexo B (~$4863K). Condicional al flujo: si facturación queda en ~$67K pediría conciliación **de inmediato**; si sube a $81.6K, evaluará tiempos/flujo en las próximas semanas. Enfoque platicado: estados de cuenta PDF → extracción (librerías/OCR/IA) → **matching por algoritmo (~98%)** usando la API de BIND para las facturas. Involucrar a **Arturo** (bancos).
3. **Dashboard/reporteo: posible recorte.** Balam **ya tiene un tablero Power BI** (hecho por Pedro) que resuelve antigüedad de cartera; Noe no quiere pagar por lo que ya tienen. **Sí quiere el módulo de cobranza + alertas internas** (que el tablero no ejecuta). Validar con Pedro qué del dashboard es redundante.
4. **Lista blanca:** confirmado que es **configurable**, no limitada a 3.
5. **Soporte/mantenimiento:** a Noe no le gusta la caducidad de horas a 30 días ("úsalo o lo pierdes"); pide **negociar mayor vigencia**. Johann: negociable.
6. **API BIND:** Pedro hoy usa la llave de **Araceli** para el Power BI; para desarrollo se usará otra (de **Arturo**, mismos permisos). BIND parece permitir **1 API key por usuario** (Pedro confirmará); generarlas desde la **cuenta maestra (ARA)** y **documentar cuál es para qué**.
7. **Azure:** se gestiona con **Guajardo / Erika**. Johann requiere permisos para crear App Service + PostgreSQL (no Global Admin).
8. **Manual de marca:** existe (paleta, tipografía, logos); **Pedro lo envía**.
9. **Reglas de negocio:** se definen con **Arturo + Pedro**.
10. **Metodología IA:** aclarado = no se entrenan modelos con datos de Balam. Noe OK.
11. **Pagos:** anticipo a **7 días** OK; **avances a 30 días** (Balam cobra a sus clientes a 60120 días; 30 es el mínimo negociado). Johann aceptó.
12. **Gestión del proyecto:** en **Jira** (estilo ágil/Yael, no Gantt). Pedro crea el espacio (plan free); **Erika valida entregables** para el pago.
13. **Comunicación:** canal de **WhatsApp** + correo para evidencia formal. **Erika = contacto principal · Pedro = técnico · Arturo = negocio.**
14. **Fiscal (Johann):** en proceso de cambiar régimen (24 h); confirma que **sí puede facturar**.
15. **Inicio:** Johann puede empezar **cuanto antes**, de preferencia inicio de semana para coordinar con Arturo.
**Próximos pasos definidos en la llamada:**
- **Noe formaliza por correo a partir del 5-jun:** solicita **factura de anticipo**, envía info/accesos y los **ajustes a la propuesta** para arrancar.
- **Johann:** cambiar régimen fiscal (24 h), emitir factura de anticipo, preparar Discovery.
- **Pedro:** enviar manual de marca; revisar llaves API (cuántas por usuario, generar desde ARA); crear proyecto en Jira.
- **Pendiente con Arturo:** reglas de negocio + acceso a bancos (conciliación).
- **Azure:** gestionar con Guajardo/Erika.
---
## 15 · Jun 8, 2026 — 6:15 PM · Correo · Noe → Johann (CC Ara, Erika, Pedro) · aclaraciones y preguntas
> Evidencia: `../fuentes/2026-06-08 - Correo - Noe aclaraciones y preguntas.md`. Re-adjunta `Propuesta-Balam.pdf`.
Noe formaliza por escrito 4 aclaraciones/ajustes y 3 preguntas, antes de avanzar:
**Ajustes solicitados:**
1. La inversión final del módulo de facturación queda **supeditada al Discovery** (112 vs 136 h, 67 sem). — *De acuerdo, así estaba planteado.*
2. **Soporte:** el límite de consumo **mensual** es limitante; piden **bolsa de horas vigente 12 meses** (paquetes por hora, no mensualidad con caducidad).
3. **Garantía:** piden **45 días** (no 30) — el ciclo financiero es mensual y los defectos no se ven hasta pasado el cierre.
4. **Sin anticipos** (política Balam) y **pago estricto a 30 días** post-factura. Proponen: Johann **factura las 30 h de la Etapa 0** y se programa el pago a 30 días.
**Preguntas:**
- ¿Qué pasa si el Discovery revela que la **API de BIND no soporta la escritura/emisión** esperada?
- Si el Discovery **reduce horas**, ¿se puede **adelantar algo de conciliación**?
- Sin sandbox, ¿qué **garantías concretas** contra una **emisión errónea con efecto fiscal**? ¿Hay **reversa/cancelación** contemplada?
**Acción Johann:** responder el correo (respuestas + postura comercial) y reflejar los cambios en la **propuesta v1.1**. Decisión clave: aceptar el esquema **sin anticipo / pago a 30 días** (impacto en flujo). Ver [PENDIENTES.md](PENDIENTES.md).
---
## 16 · Jun 10, 2026 — 6:11 PM · Johann → Noe (CC Ara, Erika, Pedro) · respuesta + propuesta v1.1
> **Asunto:** RE: Solicitud de cotización PRD
> **CC:** Araceli Sánchez, Erika Chávez, Pedro Ayala
> Evidencia: `../fuentes/2026-06-10 - Correo - Johann respuesta v1.1.md`. Adjunta `Propuesta-Balam.pdf` (v1.1).
Johann responde el correo del 8-jun, **acepta los 4 ajustes** y contesta las 3 preguntas técnicas; reenvía la **propuesta v1.1**.
**Respuesta a los ajustes:**
1. **Inversión** del módulo de facturación: de acuerdo, se confirma con el Discovery (112136 h, 67 semanas).
2. **Soporte:** de acuerdo. Replanteado como **bolsa de horas a $600/h + IVA, vigencia 12 meses** desde su contratación, sin caducidad mensual.
3. **Garantía:** de acuerdo, **45 días** (cubre el primer cierre mensual).
4. **Sin anticipo / pago a 30 días:** de acuerdo. Factura las **30 h de la Etapa 0** al inicio y el pago corre a 30 días, igual que los avances. **Única condición:** dejar **firmado el contrato/orden de trabajo antes de iniciar** (la firma formaliza el compromiso y le permite arrancar de inmediato).
**Respuesta a las preguntas:**
- **Si BIND no soporta escritura/emisión:** es lo que el Discovery valida antes de construir. Si no es viable o implica riesgo, la facturación se entrega en **modo asistido** (la plataforma prepara y valida; la emisión final se confirma en BIND) y se reajusta el alcance del módulo. No se invierte en construir algo que no funcione.
- **Si el Discovery reduce horas:** sí, la capacidad liberada puede arrancar un **primer alcance de conciliación**, con mini-scope y estimación al cierre del Discovery.
- **Garantías sin sandbox / reversa:** la emisión tiene **cinco candados** — lectura primero, validación en Discovery, dry-run antes de cada emisión, confirmación humana obligatoria por factura y feature flag apagado por defecto. La plataforma no auto-emite; el timbrado lo hace el PAC de BIND. La cancelación de un CFDI corre por el proceso fiscal BIND/SAT (con su ventana de aceptación); la plataforma puede disparar/registrar esa solicitud vía API de BIND si la soporta — a confirmar en Discovery.
> **Lectura estratégica:**
> - Johann **cede en lo comercial** (sin anticipo, 30 días, 45 días garantía, soporte 12 meses) pero **fija un candado propio: firma del contrato antes de arrancar**, que sustituye al anticipo como mecanismo de compromiso. Es el punto a vigilar.
> - Las 3 respuestas técnicas **desactivan los riesgos** que Noe planteó sin ampliar alcance: el Discovery sigue siendo la red de seguridad.
> - La conciliación queda **explícitamente condicionada** a horas liberadas por el Discovery — coherente con lo que Noe pidió el 4-jun.
---
## 17 · Jun 16, 2026 — 3:35 PM · Noe → Johann, Pedro (CC Ara, Erika) · luz verde ⭐
> **Asunto:** Solicitud de cotización PRD
> **CC:** Araceli Sánchez, Erika Chávez
> Evidencia: `../fuentes/2026-06-16 - Correo - Noe luz verde y documento de firma.md`. Adjunto: Outlook-p2vlyjhm (50 KB). _(Hora 3:35 PM tomada de la captura del correo.)_
Balam da **luz verde** a los términos de la v1.1. Noe confirma:
- *"Estamos de acuerdo con lo que se definió."* Balam **ya está redactando el documento para firmar** estos acuerdos.
- Pide a Johann **esperar** a que le envíen todo para los siguientes pasos (queda en hold del lado del proveedor).
- Instruye a **Pedro** a **armar el tablero de seguimiento en JIRA** y **revisarlo con Erika** para el seguimiento del proyecto de desarrollo, "para ir preparando el camino".
> **Lectura estratégica:**
> - **Hito comercial:** el cliente acepta formalmente la propuesta v1.1. El proyecto pasa de "negociación" a "preparación de firma".
> - La pelota está **del lado de Balam**: redactar el documento de firma. Johann debe **revisarlo cuando llegue** (verificar que recoja: sin anticipo + firma previa, 30 días de pago, 45 días de garantía, soporte bolsa 12 meses, alcance MVP BIND-first y el condicionante de Discovery) antes de firmar.
> - Se **activa el tablero Jira** (Pedro + Erika), tal como se acordó el 4-jun — primer paso operativo real.
> - No hay accesos ni fecha de arranque todavía: el Discovery no inicia hasta que llegue el documento firmado + los accesos.
---
## 18 · Jun 25, 2026 — Balam (vía Paola, RH) → Johann · contrato de servicios para firma
> Evidencia: `../fuentes/2026-06-26 - WhatsApp - Paola (RH) ajustes y firma de contrato.md`. Contrato: `../propuesta/2026_06-25_12-13__Contrato_de_servicios_profesionales__Johann_Joseph_Velazquez_Antonio.pdf`.
Balam envía el **Contrato de Prestación de Servicios Profesionales** (PDF) para firma, coordinado por **Paola (Recursos Humanos)** vía WhatsApp. Es el "documento de firma" que Noe anunció el 16-jun (#17). Recoge los términos v1.1: sin anticipo, pago a 30 días, garantía 45 días, alcance MVP BIND-first, firma como condición de arranque, propiedad intelectual de Balam, y **facturación semanal (viernes)** por horas trabajadas. Johann pide corregir su segundo nombre ("Josep" → "Joseph"); Paola corrige.
---
## 19 · Jun 26, 2026 — WhatsApp Johann ↔ Paola (RH) · ajustes finales y FIRMA del contrato ⭐
> Evidencia: `../fuentes/2026-06-26 - WhatsApp - Paola (RH) ajustes y firma de contrato.md`. Contrato firmado: `../propuesta/2026_06-26_14-19__Contrato_de_servicios_profesionales__Johann_Joseph_Velazquez_Antonio.pdf`.
Johann revisa el contrato a detalle y plantea observaciones por WhatsApp. **Balam acepta 2 ajustes** y los agrega (nueva cláusula "Terminación anticipada, pago de servicios y aceptación de entregables"):
1. **Pago al terminar:** en terminación anticipada se pagan las **horas efectivamente trabajadas** hasta la fecha.
2. **Aceptación de entregables:** se dan por aceptados si no hay comentarios por escrito en **10 días naturales**; correcciones limitadas al alcance pactado.
**Correcciones aplicadas:** firmas → "EL CLIENTE / EL PRESTADOR DE SERVICIOS" (se quitó "TRABAJADOR"); se eliminó el párrafo "Para constancia" duplicado.
**Johann FIRMA el contrato** (firma electrónica, 26-jun 14:48, RFC VEAJ031228MD6).
> **Lectura estratégica:**
> - **Cierre formal del cierre comercial.** El proyecto queda contratado; falta que Balam entregue los **accesos** para arrancar.
> - **Residuo (bajo riesgo):** la cláusula "Firma Electrónica" de la última página quedó **duplicada** y aún dice **"EL PATRÓN"** (residuo de plantilla). Opcional pedir copia limpia; el resto del contrato define bien a las partes y declara que no hay relación laboral.
> - **Términos NO incluidos** (Johann decidió no insistir): tope/límite de responsabilidad, rescisión recíproca, metodología IA, "lugar de servicios", bolsa de horas 12 meses.
> - **Facturación semanal (viernes)** por horas + 30 días — distinto del "por etapa" de la propuesta; es lo vinculante.
---
## 20 · Jun 29, 2026 — WhatsApp Erika → Johann · arranque del plan de actividades ⭐
> Evidencia: `../fuentes/2026-06-29 - WhatsApp - Erika arranque del plan de actividades.md`.
Erika (PM, contacto principal) confirma que **ya pasaron los temas administrativos internos de Balam** y arranca la coordinación de ejecución. Pide a Johann un **listado de actividades con fechas (Excel)** por **etapas** (como en la propuesta), empezando por **Etapa 0 y 1**: actividad · fecha iniciofin · responsable, **alineado a los entregables**, indicando dónde requiere apoyo de Balam. Ofrece llamada de 5 min.
> **Lectura estratégica:**
> - El proyecto entra en **fase de ejecución / kickoff**.
> - Acción de Johann: preparar el **Excel de actividades (Etapa 0 y 1)** y proponer **sesiones de Discovery** (conocer el proceso actual + encaminar el prototipo) — parte de la Etapa 0; el contrato ya obliga a Balam a dar disponibilidad de interlocutores.
> - Sigue pendiente que Balam entregue los **accesos** (API BIND, Azure, manual de marca, reglas con Arturo).
---
## 21 · Jun 2930, 2026 — WhatsApp Johann ↔ Erika · plan de actividades entregado + kickoff agendado ⭐
> Evidencia: `../fuentes/2026-06-29 - WhatsApp - Erika arranque del plan de actividades.md`. Plan: `../planeacion/Plan-actividades.xlsx` (+ `.md`).
- Johann entrega el **plan de actividades** (Excel sencillo: actividad · fecha iniciofin · responsable · apoyo de Balam): primero Etapa 0 y 1 (29-jun) y luego **enviado completo, las 4 etapas (03)** con fechas tentativas (**30-jun, 12:34**). Las sesiones quedan ubicadas por etapa; la Etapa 01 se mantiene idéntica a lo enviado el 29-jun.
- **Erika confirma que será la intermediaria** de todas las sesiones ("lo que necesites me lo pides y yo coordino agendas").
- **Kickoff con el Ing. Noé CONFIRMADO: miércoles 1-jul, 7:00 am.**
- Erika está montando el **tablero Kanban en Jira** para el seguimiento (mide avance por %).
> **Lectura estratégica:**
> - Arranca la operación: el 1-jul es el kickoff. Etapa 0 (ejecución) planeada para la **semana del 6-jul**, condicionada a accesos.
> - Erika como **punto único de coordinación de sesiones** simplifica la logística (Johann define qué necesita; ella agenda con la persona correcta).
> - Bloqueador vivo: **accesos de Balam** (API BIND, Azure, manual de marca, reglas con Arturo).
---
## Resumen ejecutivo del hilo (para contexto rápido)
### Datos duros confirmados por Balam
- **BIND ERP:** API existe y es viable (confirmado 25-may). Límite 20K req/día. PAC para CFDI integrado en BIND.
- **BUK:** ✅ tiene API (confirmado 27-may por Pedro). No es prioridad para MVP — habilita Fase 2 post-MVP. Link pendiente de recibir.
- **Bancos:** 3 bancos, solo PDFs. Banco americano = IBC Bank Texas (confirmado en llamada del 19-may).
- **Sandbox:** solo producción (BIND y BUK).
- **Volumen:** 45 colaboradores + 5 freelancers, ~50 facturas/mes.
- **EUR:** fuera del MVP; solo MXN + USD.
- **Lista blanca cobranza:** ACUNTIA + top 3, configurable.
- **Book = SaaS** (no interno).
- **Jira:** solo gestión de proyectos con clientes.
- **Nube preferida:** Azure.
- **Contacto operativo durante MVP:** Noe + gerente administrativo.
### Pendientes que Ara (Araceli) debía responder
- Banco americano específico (resuelto en llamada: IBC Texas).
- Tax compliance para clientes Texas (resuelto en llamada: estándar).
- Tickets físicos en pagos con tarjeta.
- Guía de marca / assets.
### Señales clave del CTO
1. **Llamada del 19-may** (transcript aparte): Noe pidió textualmente *"no le queremos estar poniendo estrellitas al pino, nada más estrictamente lo que se necesita"*. Foco = facturación + conciliación.
2. **Correo del 25-may**: Noe pide explícitamente que la **propuesta se recorte a "solo ERP BIND" primero**, dejando bancos/conciliación para fase posterior.
### Estado actual (al 30-jun-2026 — kickoff mañana 1-jul)
**Contrato FIRMADO (26-jun)** y **proyecto en arranque.** El **kickoff con Noé está confirmado para el miércoles 1-jul, 7:00 am.** Johann entregó el **plan de actividades** (4 etapas, fechas tentativas) en `../planeacion/Plan-actividades.xlsx`. Erika coordina el seguimiento (tablero Kanban en Jira) y es la **intermediaria de todas las sesiones**.
**Términos vinculantes del contrato:** sin anticipo + firma previa (cumplida) · **facturación semanal los viernes** por horas efectivamente trabajadas, pago a 30 días · garantía 45 días · alcance MVP BIND-first · arranque condicionado a accesos + API de BIND con lectura/escritura.
**Calendario tentativo:** kickoff 1-jul · Etapa 0 (Discovery) sem del 6-jul · Etapa 1 1324 jul · Etapa 2 27-jul7-ago · Etapa 3 1021 ago. Las fechas se confirman/afinan al cerrar el Discovery (dependen de los accesos).
**Acciones inmediatas de Johann:**
- **Asistir al kickoff (1-jul, 7am)** con material listo (agenda + lista de accesos a pedir + preguntas de Discovery).
- Cambiar régimen fiscal (para facturar) y confirmar permisos exactos de Azure.
**En espera de Balam:** entregar **accesos** (API BIND vía cuenta maestra ARA / llave de Arturo, Azure con Guajardo/Erika, manual de marca de Pedro) y reglas de negocio + bancos con Arturo. El **Discovery arranca** una vez recibidos.
**Pendiente menor:** (opcional) pedir copia limpia del contrato — la cláusula de Firma Electrónica de la última página quedó duplicada y aún dice "EL PATRÓN" (residuo de plantilla, bajo riesgo).
**Conciliación:** sigue como posible primer alcance si el Discovery libera horas (mini-scope + estimación al cierre del Discovery).
@@ -0,0 +1,245 @@
Revisión de Propuesta de desarrollo.
Thu, Jun 4, 2026
0:08 - Johann
¡Buenos días, Johann!
0:10 - Johann
¿Cómo estás?
0:10 - Johann
¿Qué tal, Leo? ¡Buenos días!
0:12 - Noe Rocha
Muy bien, ¿y tú? ¿Qué tal todo? Muy bien, gracias a Dios, también.
0:17 - Johann
Amaneciendo temprano. Sí.
0:18 - Noe Rocha
Oye, Johann, nada más como totalmente fuera de esta situación, fíjate que, no sé si te llama la atención, pero hay una empresa que se llama Freeza, que está buscando developers en front y en backend, y la oferta se me hace interesante. Entonces, Le voy a decir a Paola que te la haga llegar, si es que te llama la atención. Porque creo que encajas muy perfecto con ese tipo de puestos que están pidiendo en esta organización. Y también tengo que decir que se me hacen muy atractivos los esquemas de compensación que manejan. Entonces, para que lo considere, si es que es algo que te pueda llamar la atención. Me acordé. Gracias.
1:01 - Johann
Muy bien.
1:01 - Noe Rocha
Oye, Johann, bueno, voy a entrar directo al grano porque tengo una serie de entrevistas todo el día de aquí hasta las 12. Entonces voy a aprovechar el tiempo lo mejor posible. Aquí está con nosotros Pedro y está Erika. ¿Qué tal, Erika?
1:16 - Unidentified Speaker
Buenos días.
1:17 - Johann
Buenos días, Johann.
1:18 - Johann
Buenos días.
1:19 - Noe Rocha
Buenos días a todos. Voy a ser muy rápido para que si el que no ha desayunado se pueda ir a desayunar rápido. Mira Johann, en términos generales, la propuesta está bien. Hay algunas cuestiones que te negociaría, porque considero que se puede manejar de otra forma. Pero en términos generales, por ejemplo, el E-Discovery, el anticipo de los 18 mil, me queda claro que es para poder saber cuánto tiempo nos vamos a tardar o qué más se necesita. Porque hasta ahorita todo es platicadito y lo que hemos pasado. En esta parte yo te diría, sí, vamos por este anticipo para que empieces el E-Discovery. Yo sé que a partir de la discovery ya vas a poder determinar claramente la inversión en tiempo que se maneja aquí. Porque es un supuesto todavía por los adicionales que pueden salir, que se puede ir de 67 a 81 mil, dependiendo de lo que encuentres. Cierto? Sí. Bueno, hasta ahí todo correcto. Entonces surgió otro tema cuando me dijeron oye, incluye conciliación? No. Esto en un principio no estaba. Correcto. Esto es solo para facturación, ¿verdad?
2:28 - Unidentified Speaker
Sí.
2:28 - Noe Rocha
Preparado para las demás cosas que pedimos, como la conciliación. Porque me están pidiendo que sí. Que considere la parte de conciliación. ¿Dónde está? ¿Dónde está? Requerimiento funcional. Por aquí lo pusiste. Y pusiste hasta el costo, si mal no recuerdo. ¿Correcto?
2:49 - Pedro Alberto Ayala Elizondo
Sí, son como $48.63.
2:51 - Noe Rocha
Para la conciliación? Para los módulos adicionales. Entonces, la parte de conciliación, sí me interesaría incluirla, pero sí quisiera saber ya, por ejemplo, cómo se establece reporte de cierre, facturación, eso sí está. ¿Dónde quedó lo de conciliación?
3:18 - Johann
¿Están en los anexos B?
3:21 - Noe Rocha
¿Están abajo, verdad? Sí. A ver. Drop, activación. Página 8, más o menos, creo.
3:29 - Pedro Alberto Ayala Elizondo
Página 8, página 13.
3:32 - Unidentified Speaker
Conciliación.
3:32 - Unidentified Speaker
Aquí está. 15.
3:34 - Pedro Alberto Ayala Elizondo
Y le pones un monto.
3:37 - Noe Rocha
Porque te voy a platicar. Si al final son... Vamos a inventar algo. Si al final mejor son 60 y tantos mil que es lo que tú pusiste para hacer la parte de facturación y ya con el discovery me podrías esto no sé qué otra cosa podrías poner sobre la mesa para estar seguro que el tema de conciliación pues si me gustaría entonces tener en cuenta hacer las dos no conciliación y facturación de una vez no solamente esperarnos a la par no solamente ser pura facturación Porque hoy por hoy la conciliación sí se está haciendo realmente un tema problemático. ¿Cómo se hace la conciliación? Pues es bien manual, Johann. No hay que ser muy listos para saber que si no tienes un sistema, una integración, no te queda otra más que básicamente bajar el estado de cuenta, después revisar el estado de cuenta, de cuenta me llegó 10.857.3234 ok y como no tiene referencia pues no sabemos cuál se pagó suponiendo que tengamos las mismas tarifas con algunos proveedores o clientes más que la referencia o la referencia del emisor y ya viste el estado de cuenta que a veces viene mucho a veces ni siquiera viene con todas las leyendas necesarias y nuestros clientes van a ponerle la referencia que se les hinche la gana porque Por más que nosotros le digamos, oye, por favor, pon una. Ellos lo hacen como les da a entender y como su sistema lo tiene. No se van a adecuar a nada. Así es. Y ya te pague. No pocas palabras. Bueno, entonces bajo un estado de cuenta me voy al RP. Digo a ver, coincide. Pues sí, al menos por monto. Sí, verdad? A ver, es el mismo proveedor. Pues híjole, no trae suficiente información, pero por la fecha, por el monto y por lo que nos dijo el proveedor, pero el cliente pues se Entonces, esa parte de conciliación es muy manual. Y la verdad, ya nos está causando un problema. Por todo lo que te he contado. Entonces, lamentablemente, los PDF bancarios no tenemos integración. O no sé cómo se puede hacer una integración directamente con los bancos. Los bancos ofrecen integraciones, pero es un rollo. Yo creo que al final va a ser más sencillo. Bueno, bajo mi estado de cuenta, lo proceso en algún sistema. Y que más o menos me saque de mis facturas una relación o una similitud por lo menos a nivel algoritmo ya no estoy pidiendo que haga una validación exacta sino más bien diga bueno pues por un algoritmo puede ser que más menos se parece y es un 98% seguro de que es esta pago corresponde a esta factura para que ya el factor Si me explico, así es como lo estoy pensando. No sé cómo tú lo habías pensado. Sí, así como lo mencionas, la integración con bancos puede ser muy tardada. Y eso era lo que me temía al principio cuando vi el PRD.
6:47 - Johann
Porque, por ejemplo, en el trabajo en el que estoy, estamos con Juanpa Norte, y creo que es el que más me interesaba. Y es el que más me interesaba. Y es el que más me interesaba. Y es el que más me interesaba. Tiene mucha seguridad ¿no? En cuanto a sandboxes, en cuanto a conseguir API keys y toda esa parte y también el costo ¿no? Entonces esta parte que mencionas de manejar los PDFs y entiendo que la conciliación se trabajaría con el API de Bint ¿cierto? Entonces... Sí, o sea el API de Bint me va a decir ¿estas son las facturas?
7:21 - Noe Rocha
no sé tengo 30 facturas y mis diferentes bancos porque algunos me van a pagar por uno y algunos me van a pagar por otro o claro no me van a pagar siempre por el mismo, pero pues ya más o menos puedo darme una idea de decir ah bueno que ya tengo Estados Unidos, son dólares, pues de cajón ahí ya mato por moneda, digo bueno yo todo lo que no es todo lo que no es moneda mexicana no lo ignoro y me aboco a ver las facturas en dólares, ah bueno pues ya de las facturas que son en dólares ahora sí cierro la pinza y veo la coincidencia y el resto los van en moneda nacional que son otros. Pero si es básicamente sacar los estados de cuenta y mandarlos al mandarlos al este por pdf al como te decía este al. A la revisión, a la a la al proceso de ver cómo qué tan similares son ya con vain que vain es el que si me dice por bueno, yo esperaría que el API me traiga exactamente qué factura ese dato si no lo da, verdad?
8:25 - Pedro Alberto Ayala Elizondo
Pedro de los datos de los estudios porque he estado viendo el tema de la facturación si es que mira yo aquí lo que pasa es que hay hoy por hoy en dentro de 20 hay ciertos módulos que no se terminan de alimentar al 100% entonces yo hice una conexión con un tablerón por día que es mucho más sencillo de lo que aquí se plantea pero estuve jugando con ciertos con cierta extracción y me arrojaba mucho contenido vacío, entonces sí es conveniente poder explorar qué módulos son los que en verdad vamos a tener pues carga de información, pero si el tema de facturación, ese sí está completo, ese sí está bastante manejable por así decirlo.
9:17 - Johann
Ok, entiendo. Sí, muchas gracias por la información. Igual, complementándolo ahorita, con las facturas, bueno, con lo de la conciliación. También habría que ver que todo lo que les mandan los clientes está en el mismo formato. Entiendo que de pronto uno puede enviar un PDF o una imagen. Entonces, ahorita lo que se podría ocurrir con ese algoritmo que comentas podría, por ejemplo, hay varias librerías para manejar PDFs y extraer texto para este no proceso involucrar de porque pronto solamente modelos sería extracción de inteligencia para texto o podríamos utilizar OCR de pronto por ahí. O sea, sí se me ocurren varias estrategias y habría que analizar el formato en el que reciben estas. Estados de cuenta.
10:06 - Noe Rocha
Sí, esos estados de cuenta. Sí. Nada más involucrar a Arturo, que es el que ve todo el tema de los bancos. Pero básicamente es eso, Johann. Otro tema que te quería decir de esta propuesta es la parte de... El reporteo que propones aquí para las cuentas, para lo que es la facturación, por aquí lo mencionabas. Cobranza, tabla de reportes, desierro. Sí, que básicamente nos da la antigüedad de la cartera y demás. De alguna forma esa parte ya la hemos resuelto con Pedro, pero lo que no hace el tablero de pobre es evidentemente ejecutar una tarea o una alerta, que eso es lo que tú propones aquí, ¿verdad? Las ventas internas para finanzas. O sea, una parte es el que nos diga el módulo de cobranza, ¿Cómo van las facturas con más eficiencia? Que eso me parece muy bien. Aquí, cuando hablas de acunte y top 3, ¿estaría limitado solamente a estos 3 inicialmente? ¿A 3 proveedores? ¿O cómo lo estás pensando?
11:22 - Johann
No, no está pensado solamente para 3 proveedores. Sino que se trata de una lista blanca que ustedes pueden configurar en un futuro.
11:32 - Noe Rocha
Ah, ok. Ok muy bien y este muy bien esta parte de reporte operativo configurable no estoy tan seguro que tanto provecho le pueda sacar a Arturo este ya con el tablero Power BI esto no te quiero negociar que el precio yo lo que te quiero decir es que lo estamos resolviendo ahorita de otra forma porque el resto de lo que viene aquí pues si me interesa va este y sobre todo que lo entienda Pedro porque si va a haber tema de de tableros. Bueno, en general, en todo este tema va a estar metido Pedro contigo, ¿no? En general. Pero ver que hace sentido de lo que ya hace Pedro, y que si ya Pedro lo tiene digerido y resuelto, a lo mejor no es necesario hacer como tal el tablero, pero sí el resto de las cosas.
12:22 - Noe Rocha
¿Me explico?
12:22 - Noe Rocha
Sí, entiendo.
12:23 - Noe Rocha
Bueno, y otro tema que te quería decir. Otro tema que te quería mencionar es el soporte. Me gusta mucho, pero no me gusta que me condicione a que si no me lo acabo, lo pierdo. Generalmente cualquier proveedor, cuando tú le compras un paquete de horas de servicio, pues sí tiene una vigencia. Tampoco son infinitas, eso sí lo entiendo. Pero un mes se me hace muy poco. No estoy diciendo que no lo vamos a acabar, o que nos van a faltar, o que te vamos a tener ahí parado con las horas, pero tampoco quisiera verlo del otro lado. Si yo te compro un paquete premium de horas y hay una condicionante de tiempo para usarla, yo no sé si es poquito o es mucho, la verdad no lo sé, pero lo que sí te diría es vamos a negociar el plazo de cuánto tiempo me va a durar estas horas, porque si me a 30 días, pues mejor te pago a estajo. Sí me explico.
13:25 - Unidentified Speaker
Ok.
13:26 - Noe Rocha
O sea, haría más sentido. Y eso te lo digo cuando lo vayas a negociar con otros otros clientes. Generalmente es yo y así implica casi en muchos lados. Tú pagas las horas. Si tienen una vigencia, pero si son este, es muy corto el plazo de inicio. Puede sonar como que oye, pues no estoy seguro porque no sé cuánto me voy a acabar, porque ni siquiera hay un dato de cuánto he requerido de servicio de esto y entonces te van a decir pues te pago destajo mejor te pago conforme trabajas y puede ser en todo caso una manera de iniciar yo te diría este tema de las horas te lo voy a poner por escrito pero yo mínimamente si ocuparía que estuviera tuviera más de lo que estás manejando aquí como tiempo término de duración de vigencia explico a menos de que tú tengas una razón muy específica y de por ¿Por qué lo quieres manejar así? Entiendo.
14:23 - Johann
Totalmente es negociable. Y totalmente entiendo esa parte de que los 30 días puedan ser poco. Y sí, sin problema, se puede negociar a un plazo que les parezca conveniente.
14:35 - Noe Rocha
OK. Muy bien. Y creo que de ahí en fuera, pues, todo lo demás se entiende. Pues, estos son costos estimados de acuerdo al uso, al storage. Está entendido que es. Lo que va a costar tener este servicio en la nueva. 160, 30 dólares aproximadamente va a depender de varias cosas, crecimiento y demás. ¿Dudas tuyos, Pedro? De momento no. De momento no. Y bueno, obviamente las dependencias, que tengas el acceso al API, que lo único que sí he notado es que tiene cierta limitante en el número, ¿La cantidad de solicitudes? Que no sé si tú has llegado a algún límite o has estado monitoreando eso, Pedro, para el tema de los dashboards, por ejemplo.
15:25 - Pedro Alberto Ayala Elizondo
No, aún no. Como tal, la carga de datos hoy por hoy la he estado haciendo manual. O sea, bueno, la actualización de datos. Ya tengo lo que es el lugar correcto para poder hacer el monitoreo de Bind. Y ya cuento con las credenciales de acceso. Entonces nos podemos reunir para poder ver cómo está el... El consumo de cada búsqueda, por así decirlo.
15:47 - Noe Rocha
Yo sí me gustaría nada más, Pedro, que tengas identificado qué llaves se utiliza para qué, porque tú tienes una API para dashboard de Power BI, pero para el tema del desarrollo sí sería una API diferente.
16:01 - Pedro Alberto Ayala Elizondo
Sí, porque son llaves diferentes aquí, yo estoy utilizando, si no mal me recuerdo, la llave de Araceli, del perfil de Araceli, y yo creo que en este caso estaríamos utilizando la de Arturo que es el que tiene los mismos permisos.
16:17 - Noe Rocha
Pero según yo se puede hacer más de un API o nada más es uno por usuario?
16:23 - Pedro Alberto Ayala Elizondo
Lo reviso, lo reviso porque en la parte de la configuración de la API de Vine, dentro de la configuración sólo dejaba generar una. Por usuario?
16:33 - Unidentified Speaker
Por usuario.
16:34 - Noe Rocha
Entonces nada más habría que definir.
16:36 - Pedro Alberto Ayala Elizondo
Habría que revisar si Arturo puede generar otra o ARA puede generar otra?
16:41 - Noe Rocha
Yo creo que en este caso que sea, si desde la cuenta de ARA se puede generar más de una pi, que salgan de ahí todas. Que salgan de ahí, porque son las cuentas maestras, no? Nada más documentar cuáles y no confundirla con la de los tableros.
17:00 - Noe Rocha
Muy bien.
17:01 - Noe Rocha
Entonces, bueno, y lo otro, la cuenta de Azure Equivalent, este, Ese tema lo vemos con Guajardo, Erika. Pero ¿qué tipo de permiso ocuparías? Porque no, no, o sea, me queda claro que no es un Global Admin, es otro tipo de permiso, pero ¿sabes más o menos qué tipo de permiso, para no andar adivinando, Johann, es el requerido?
17:25 - Johann
Por ejemplo, pudiésemos una cuenta que, con la que pudiese crear los servicios que vamos a utilizar. Por ejemplo, el AppLink. Al momento aquí tengo el nombre de servicio, pero para crear aplicaciones en donde vamos a ajustear el backend y frontend, por ejemplo el Azure App Service y el Azure Database Postgre, estaría bien. Igual esta parte es cuando vayamos a montar ya la aplicación sobre Azure durante el desarrollo.
17:56 - Noe Rocha
No voy a estar trabajando sobre Azure, pero una cuenta que tenga ese permisos para gestionar esos servicios ok el manual de marca si tenemos que es este cuando tenemos un un este un documento de paleta de colores tipo de letra logotipos a eso yo lo tengo ese y ese te lo va a hacer llegar Pedro las reglas del negocio pues esto sí tendríamos que verlo con arturo Pedro ok la disponibilidad es que va a ser tuya y de Arturo para resolver y desbloquear en menos tiempo las situaciones que presenta Johann. ¿Producción? Bueno, sí, sí. O sea, no hay sandbox combined, todo se extrae. ¿Acuerdo de uso de metodología de asistencia pura? Ok, está bien. Y este acuerdo, nada más para entender, tenemos ¿A qué te refieres con acuerdos de uso de metodología asistida? Yo entiendo que tú vas a usar un componente de... No sé cuál usas. De acuerdo. Si no lo usas, de hecho, me haría raro. Pero... ¿Qué me quieres decir con esta parte? Ah, el compromiso dentro del entrenamiento. O sea, que no vamos a usar los datos de nosotros para entrenar al modelo.
19:19 - Unidentified Speaker
Así es.
19:20 - Noe Rocha
Ok, ya.
19:20 - Unidentified Speaker
Listo.
19:21 - Noe Rocha
Ya entendí. El pack integrado de Vine es SteinVine. Y el comportamiento estructural de AlpineVine. En E-Discovery. Yo sé que muchas cosas van a salir con el E-Discovery. Y ya con eso podemos aclarar el monto, Johann. Vamos por la parte del anticipo. Y yo te adelanto que si nos quedamos en este primer número de inversión, te voy a pedir de conciliación de forma inmediata. Si se va a 81.600, se me hace que voy a tener que ver cómo lo hacemos porque hay un tema de flujo y a lo mejor nos tardaremos más tiempo. Pero eso te lo digo en las siguientes semanas porque Si nos interesa empezar. Tengo otra pregunta, Johann. ¿Puedes facturar?
20:15 - Johann
Estoy en proceso de cambiar mi régimen fiscal. Tengo 24 horas para cambiarlo. Pero sí, cuenta con eso. Puedes facturar.
20:27 - Noe Rocha
El anticipo no es un problema. El dinero para empezar a hacer los discoveries y demás. Y ya por correo te mandaría la información para ir formalizando, Johann. Además, dame una chancita, porque el diario está súper cargado. Además, sé que te lo vamos a mandar a partir de mañana. Ok, sí, sin problema. Ok, ya una vez con anticipo y todo, ¿qué día tú podrías iniciar? Podría comenzar cuanto antes.
21:03 - Johann
Ok, muy bien. Por ejemplo para tener el inicio de semana y poder acomodar los tiempos principalmente para por ejemplo mencionan a Arturo cierto para poder tener esas reuniones y poder que me puedan compartir cómo es que hoy en día usan el sistema por ejemplo el proceso que ya está hecho como por ejemplo el tablero de Pedro y todas esas cuestiones para poder yo trabajar en el sistema.
21:35 - Noe Rocha
Aquí nada más tendría otra cosa, tendríamos que manejarlo esto en un, yo creo que en Jira, Pedro, hacer un proyecto en Jira, para ir metiendo ahí los avances y que Erika lo vaya validando para después tener, bueno, no más la trazabilidad del tiempo y la documentación entregada y los avances entregados. Hay un tema aquí con el pago, nada más, lo que viene siendo los días de pago, Johann. La verdad es que sí, o sea, el anticipo sí, sí puede salir en los 7 días, así tal cual lo mencionas. Pero los avances sí se van a ir a 30 días, porque traemos un tema, no es un tema particular con nosotros, es un tema particular con cualquier cliente o proveedor. Imagínate que a mí me pagan a 120 días. Te lo digo muy abiertamente porque lo vas a ver. Y hay otros que me pagan a 90 y a 60 días. Entonces, la única forma que tenemos nosotros de sortear esos largos tiempos, pues también es negociar con estos proveedores los tiempos. No te voy a estirar a esos tiempos porque te voy a matar. Pero sí te voy a decir que lo mínimo que pagamos después del anticipo, que ese sí lo podemos dar en 7 días, las facturas que nos vayas dando conforme vayan dándose, ¿Tienes algún problema con eso? Ok, no. Ok, solo para estar en ese sentido. Y yo creo que eso es todo. Erika, ¿comentarios?
23:10 - Erika Chavez
Nada más aquí una duda si se va a llevar en Gantt como proyecto tal cual o nada más en Jira como soporte para la medición de los entregables y del pago de Johann, si mal no recuerdo, esto se va a manejar como entregables de manera rápida, como si fuera Yael, ¿no?
23:36 - Noe Rocha
Sí. La metodología sería en ese sentido, nada más para estar entendido, ¿ok? Sí. No va a ser GAN, Erika, va a ser JIRA como proyecto Yael, y ahí lo estaremos metiendo.
23:52 - Erika Chavez
Ok, de acuerdo. Me voy a basar para los entregables de lo que dice aquí la propuesta, que esté el documento que se va a entregar para el pago.
24:05 - Noe Rocha
Sí, y nada más que sí comparíamos que Pedro genere el espacio específico donde va a estar Johann.
24:13 - Erika Chavez
Ah, pero no me acuerdo si tenemos licencias, Pedro.
24:18 - Pedro Alberto Ayala Elizondo
No, pero no va a ser Kanban, ¿verdad? Pero también entra dentro del plan free. Sí, se puede gestionar algo sencillo con eso.
24:28 - Noe Rocha
Bueno, así que lo validamos para mandarle la información y accesos a Johann de lo que va requiriendo. Y construir ahí al tablero para, pues nada más, la metodología de trabajo aquí con Johann.
24:44 - Unidentified Speaker
Claro.
24:45 - Noe Rocha
Bueno, pues de mi parte es todo. Ya por correo te vamos formalizando el resto, Johann.
24:53 - Johann
Dime. Ah, no, tenía una pregunta acerca de la forma de comunicación. ¿Sería por correo? ¿De pronto por Teams?
25:01 - Pedro Alberto Ayala Elizondo
¿Vamos a hacer un canal de WhatsApp?
25:04 - Johann
Sí, me parece bien.
25:05 - Noe Rocha
Yo creo que sí, vamos a hacer un canal específico para este tema. Y bueno, ya para el seguimiento muy puntual, pues Erika. Erika como principal contacto. Y Pedro para la parte técnica y Arturo para la parte de negocio.
25:22 - Pedro Alberto Ayala Elizondo
Sí, Johann, tú la comunicación conmigo la puedes tener directamente por Whatsapp para facilitar, para hacerlo más ágil. Y ya cualquier cosa que se ocupe de documentar, que se ocupe de palabrar, ya nada más te pediría que se suba correo, que se suba algún algo de evidencia.
25:40 - Noe Rocha
Ok, me parece bien. Muy bien. Bueno, pues sería todo, Johann. Por correo te vamos formalizando. Y para el anticipo sí necesitaría la factura. Pero te digo, hoy no nos va a dar tiempo, seguramente mañana. Y a partir de mañana ya empezamos a pedirte la factura de anticipo y los ajustes de la propuesta para arrancar.
26:04 - Johann
Sí, sin problema.
26:06 - Noe Rocha
¿Está bien?
26:06 - Johann
Está bien.
26:07 - Noe Rocha
Bueno, gracias a todos por su tiempo.
26:10 - Johann
Muchas gracias.
26:11 - Unidentified Speaker
Con permiso.
26:12 - Unidentified Speaker
Bonito día. Igualmente.
26:13 - Unidentified Speaker
¡Gracias!
@@ -0,0 +1,27 @@
# Correo — Noe Rocha → Johann (aclaraciones y preguntas)
**De:** Noe Rocha <noe.rocha@balamtalentoestrategico.com>
**Para:** Johann · **CC:** Araceli Sánchez, Erika Chávez, Pedro Ayala
**Fecha:** Lun 8 jun 2026, 06:15 PM
**Adjunto:** Propuesta-Balam.pdf (2 MB)
---
Buenas tardes Johan,
Sobre la sesión que tuvimos me permito hacer algunas aclaraciones antes de avanzar.
1. La inversión final por el módulo de facturación está supeditada al "Discovery" en Bind para determinar si son **112 u 136 horas** en un tiempo de 6 a 7 semanas.
2. El paquete mensual de mantenimiento y soporte nos interesa, sin embargo, consideramos que el límite de consumo mensual para usarlas es limitante, por lo que pedimos que estos sean **vigentes por 12 meses** a partir de la fecha en que se contratan, es decir, **manejar paquetes de soporte por hora**.
3. Sobre las garantías de defectos de solo 30 días, lo normal es que el uso y operación en sistemas financieros tengan un ciclo mensual, por lo que los defectos o errores iniciales no serán visibles hasta pasado el cierre, es por esto, que, pedimos que la **garantía sea de 45 días**.
4. Por política en Balam **no manejamos anticipos** como concepto, y el **pago es estrictamente a 30 días** después de la factura, lo que podemos hacer es que **factures las 30 horas de la etapa 0 y programemos el pago a 30 días**.
Adicional tengo algunas preguntas:
- Si Discovery revela que la **API de BIND no soporta la escritura (emisión)** como se espera, ¿qué pasa?
- Si después del Discovery por otro lado se reducen las horas estimadas, **¿se puede adelantar algo de conciliación?**
- Sin sandbox de BIND, **¿qué garantías concretas hay contra una emisión errónea con efecto fiscal?** ¿Hay reversa/cancelación contemplada?
Quedo a la espera de tus comentarios.
Saludos.
@@ -0,0 +1,27 @@
# Correo — Johann → Noe Rocha (respuesta a aclaraciones + propuesta v1.1)
**De:** Johann Velazquez <johann_antonio85@hotmail.com>
**Para:** Noe Rocha · **CC:** Araceli Sánchez, Erika Chávez, Pedro Ayala
**Fecha:** Mié 10 jun 2026, 06:11 PM
**Asunto:** RE: Solicitud de cotización PRD
**Adjunto:** Propuesta-Balam.pdf (2 MB) — propuesta v1.1
---
Hola Noe, gracias por las aclaraciones, todas me parecen razonables. Te respondo punto por punto:
1. De acuerdo: la inversión final del módulo de facturación se confirma con el Discovery (112136 h, 67 semanas).
2. Soporte: de acuerdo. Lo replanteo como una bolsa de horas a $600/h + IVA con vigencia de 12 meses desde su contratación, sin caducidad mensual.
3. Garantía: de acuerdo, la extiendo a 45 días para cubrir el primer cierre mensual.
4. Sin anticipo y pago a 30 días: de acuerdo, me ajusto a su política. Facturo las 30 h de la Etapa 0 al inicio y el pago corre a 30 días, igual que los avances. Lo único que pediría para arrancar sin anticipo es dejar firmado el contrato/orden de trabajo antes de iniciar; la firma formaliza el compromiso de ambas partes y me permite comenzar de inmediato.
Sobre tus preguntas:
• Si el Discovery revela que la API de BIND no soporta la escritura/emisión esperada: es justo lo que el Discovery valida antes de construir. Si la escritura no es viable o implica riesgo, la facturación se entrega en modo asistido (la plataforma prepara y valida los datos) y la emisión final se confirma en BIND y reajustamos el alcance de ese módulo con lo encontrado. No se invierte en construir algo que no funcione.
• Si el Discovery reduce horas: sí, la capacidad liberada puede arrancar un primer alcance de conciliación. Lo definimos con un mini-scope y su estimación al cierre del Discovery.
• Garantías sin sandbox y reversa: la emisión tiene cinco candados: lectura primero, validación en Discovery, simulación (dry-run) antes de cada emisión, confirmación humana obligatoria por factura y feature flag que arranca apagado. La plataforma no auto-emite; el timbrado lo hace el PAC de BIND. Sobre la cancelación: un CFDI se cancela por el proceso fiscal de BIND/SAT (con su ventana de aceptación); la plataforma puede disparar y registrar esa solicitud vía la API de BIND si la soporta, lo cual confirmamos en Discovery.
Con esto ajusto la propuesta (v1.1) reflejando los cuatro puntos y se las reenvío. Quedo atento al contrato y a los accesos para arrancar el Discovery en cuanto esté firmado.
Saludos,
Johann Velazquez
@@ -0,0 +1,15 @@
# Correo — Noe Rocha → Johann, Pedro (luz verde + documento para firma)
**De:** Noe Rocha <noe.rocha@balamtalentoestrategico.com>
**Para:** Johann, Pedro Ayala · **CC:** Araceli Sánchez, Erika Chávez
**Fecha:** Mar 16 jun 2026, 03:35 PM
**Asunto:** Solicitud de cotización PRD
**Adjunto:** Outlook-p2vlyjhm (50 KB)
---
Buenas tardes Johan,
Ya estamos trabajando el documento para firmar estos acuerdos, estamos de acuerdo con lo que se definió, te pido esperar a que te mandemos todo para siguientes pasos. @Pedro Alberto Ayala Elizondo por favor haz el tablero de seguimiento para esto en JIRA y revisarlo con Erika para el seguimiento de este proyecto de desarrollo para ir preparando el camino.
Saludos.
@@ -0,0 +1,38 @@
# WhatsApp — Johann ↔ Paola (Recursos Humanos, Balam) · ajustes y firma del contrato
**Canal:** WhatsApp · **Fechas:** 2526 jun 2026
**Tema:** Revisión, ajustes finales y firma del Contrato de Prestación de Servicios Profesionales.
**Contrato (sin firmar):** `../propuesta/2026_06-25_12-13__Contrato_de_servicios_profesionales__Johann_Joseph_Velazquez_Antonio.pdf`
**Contrato (FIRMADO):** `../propuesta/2026_06-26_14-19__Contrato_de_servicios_profesionales__Johann_Joseph_Velazquez_Antonio.pdf`
---
## Resumen
- **25-jun:** Paola (RH) envía el contrato listo para firma. Johann detecta que su segundo nombre aparecía como "Josep" → corregido a "Joseph".
- **26-jun:** Johann comparte por WhatsApp sus observaciones tras revisar el contrato a detalle:
- **Correcciones:** (1) en firmas aparecía como "trabajador" / Balam como "patrón" → debe ser "prestador de servicios" / "cliente"; (2) párrafos duplicados en la última página (el de "Para constancia" y la cláusula de Firma Electrónica).
- **Propuestas:** (1) pago de horas trabajadas al terminar anticipadamente; (2) definir la aceptación de entregables + que las correcciones sean sobre el alcance.
- **Balam acepta las 2 propuestas** (consultadas por Paola con el área que decide), con esta redacción:
- *"En caso de terminación anticipada, EL CLIENTE pagará las **horas efectivamente trabajadas** hasta la fecha de terminación."*
- *"Los entregables se considerarán aceptados si EL CLIENTE no emite comentarios por escrito dentro de los **10 días naturales** siguientes a su recepción; las correcciones se limitan al alcance pactado."*
- **26-jun 14:48:** Johann **firma el contrato** (firma electrónica; RFC VEAJ031228MD6).
## Qué quedó en el contrato firmado
- ✅ Se agregaron las 2 propuestas (cláusula "Terminación anticipada, pago de servicios y aceptación de entregables").
- ✅ Firmas → "EL CLIENTE" / "EL PRESTADOR DE SERVICIOS" (se quitó "TRABAJADOR"); se eliminó el párrafo "Para constancia" duplicado.
- ⚠️ **Residuo (bajo riesgo):** la cláusula "Firma Electrónica" de la última página quedó **duplicada** y aún dice **"EL PATRÓN"** (residuo de plantilla). Opcional pedir copia limpia, pero el resto del contrato define correctamente a las partes y declara que no hay relación laboral.
- ️ El contrato fija **facturación semanal (viernes)** por horas efectivamente trabajadas + pago a 30 días (en la propuesta era "por etapa"). Vinculante.
- ️ Puntos que Johann decidió **no** insistir (no entraron): tope/límite de responsabilidad, rescisión recíproca, mención de metodología IA, redacción de "lugar de servicios", y dejar asentada la bolsa de horas (12 meses).
## Transcripción (extracto)
> **Pao (25-jun):** ya quedó tu contrato listo para firma :)
> **Johann:** mi segundo nombre aparece como "Josep" en lugar de "Joseph", ¿hay problema?
> **Pao:** ahorita corrijo… ya quedó :)
> **Johann (26-jun):** *Correccioncitas:* en firmas quedé como "trabajador" y Balam como "patrón" (es contrato de servicios, iría como "prestador"/"cliente"). En la última página se repiten el párrafo de "Para constancia" y la cláusula de Firma Electrónica (y las dos versiones dicen cosas distintas).
> **Johann:** *Un par de cosas que me gustaría proponer:* que si el contrato se termina antes, se paguen las horas y entregables ya trabajados; y definir cómo se "aceptan" los entregables (deemed-acceptance) con correcciones sobre el alcance.
> **Pao:** me dicen que lo podemos agregar así → "horas efectivamente trabajadas hasta esa fecha" y "aceptados si no hay comentarios por escrito en los próximos 10 días naturales, correcciones sobre el alcance acordado". ¿Cómo ves?
> **Johann:** me parece bien :)
> **Johann (26-jun, 2:50 pm):** te confirmo que ya firmé el contrato.
@@ -0,0 +1,26 @@
# WhatsApp — Erika Chávez (PM, Balam) → Johann · arranque del plan de actividades
**Canal:** WhatsApp (+52 1 81 2353 5803) · **Fecha:** 29 jun 2026, ~3:35 PM
**Tema:** Kickoff de ejecución — Erika solicita el plan de actividades con fechas para dar seguimiento puntual.
---
## Resumen
- Erika confirma que **ya pasaron los temas administrativos internos de Balam** y arranca la coordinación del proyecto (entra de lleno como PM / seguimiento).
- Pide a Johann un **listado de actividades con fechas (Excel)**, organizado por **etapas** (como en la propuesta), empezando por **Etapa 0 y 1**.
- **Formato pedido:** tabla sencilla con **actividad · fecha inicio fecha fin · responsable**, **alineada a los entregables** de la propuesta (cada actividad debe conducir a un entregable). Indicar si la actividad es solo de Johann o requiere **apoyo de Balam**.
- Ejemplo que dio: *"levantamiento de plataformas activas con el usuario XX-XX al XX-XX"*.
- Ofrece una **llamada de 5 min** para aclarar el formato.
## Acciones de Johann
- [ ] Preparar el **Excel de actividades (Etapa 0 y 1)** — actividad, fechas, responsable, alineado a entregables; marcar dónde se requiere apoyo de Balam.
- [ ] Proponer **sesiones de Discovery** (parte de la Etapa 0): conocer el **proceso actual** de facturación/cobranza (cómo operan hoy, qué sale de BIND, reglas) y **encaminar el prototipo**. El contrato ya obliga a Balam a dar disponibilidad de interlocutores para consultas/aprobaciones.
## Transcripción (extracto)
> **Erika:** ya pasaron todos los temas administrativos internos Balam, quisiera ver contigo las actividades del proyecto… necesito un listado de acts y fechas para el seguimiento puntual. ¿Por mes o por fases?
> **Johann:** me parece bien por fase.
> **Erika:** manejamos por etapas como en tu propuesta; pásame un Excel de las actividades de esta primera etapa con fechas. Tabla con nombre de actividad, fecha inicio fecha fin y responsable. Los entregables ya vienen en la propuesta, solo que las actividades queden alineadas a ellos. Indica si es tu actividad o necesitas apoyo nuestro.
> **Johann:** te comparto el Excel de la Etapa 0 y 1.
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
# Kickoff del proyecto — mié 1-jul-2026, 7:00 am (con Noé)
> Guion para Johann. Sesión de arranque del MVP de automatización financiera (BIND-first). Por Teams. ~4560 min.
## Objetivo de la sesión
Alinear arranque, confirmar accesos y fecha de inicio formal, y dejar agendadas las sesiones de Discovery. No es sesión técnica profunda — es para destrabar el inicio.
## Agenda (sugerida)
1. **Recap rápido** del alcance (2 min): MVP de facturación (consulta + emisión asistida MXN/USD) y cobranza sobre la API de BIND; 4 etapas, ~67 semanas.
2. **Fecha de inicio formal** (5 min): por contrato arranca cuando estén firma ✓ + accesos + API BIND con lectura/escritura. Meta: **Etapa 0 la semana del 6-jul**.
3. **Accesos** (10 min): repasar la lista de abajo — quién provee qué y para cuándo.
4. **Discovery** (5 min): agendar con Erika la sesión del **proceso actual** (facturación/cobranza) y la de **reglas de negocio con Arturo**.
5. **Mecánica de trabajo** (5 min): Jira/Kanban (Erika), demos los viernes, reporte semanal de horas, facturación semanal, comunicación WhatsApp + correo.
6. **Próximos pasos y responsables** (3 min).
## Accesos a solicitar (el bloqueador del arranque)
- [ ] **API de BIND**: llave con permisos de **lectura y escritura** + documentación. Generar desde la **cuenta maestra (ARA)**; ¿cuántas llaves por usuario? (la del Power BI es de Araceli; la de dev sería de **Arturo**). — *Pedro/Arturo*
- [ ] **Azure**: suscripción o autorización para crear la infra a nombre de Balam; permisos para **App Service + PostgreSQL Flexible + Key Vault + Storage + Application Insights** (no se requiere Global Admin). — *Guajardo / Erika*
- [ ] **Manual de marca** (paleta, tipografía, logos) para el prototipo. — *Pedro*
- [ ] **Reglas de negocio**: lista blanca (**ACUNTIA + Top 3** exactos), ciclos de cobranza, días de anticipación de alertas, frecuencia de sync. — *Arturo*
- [ ] **Contacto operativo** que muestre el proceso actual de facturación/cobranza (para la sesión de Discovery). — *vía Erika*
## Preguntas de Discovery (para la sesión del proceso actual)
**Facturación**
- ¿Cómo se genera hoy una factura, paso a paso? ¿quién y en qué sistema?
- ¿Hacen cotización → factura dentro de BIND? ¿qué datos capturan a mano y de dónde salen?
- Clientes en USD/extranjeros: ¿cómo manejan IVA/sin IVA y el tipo de cambio?
- Volumen real (~50/mes) y picos.
**Cobranza**
- ¿Cómo dan seguimiento hoy a las cuentas por cobrar? (rol del **Power BI** de Pedro)
- ¿Cómo registran los pagos y de dónde ven el saldo por factura?
- Lista blanca: ¿quiénes son ACUNTIA + Top 3 y qué regla de recordatorios aplica?
- ¿Qué alertas necesitan y para quién?
**BIND / técnico**
- Estructura de datos en BIND: campos de **saldo, vencimiento y estado** por factura.
- ¿Quién administra la cuenta maestra (ARA)? ¿llaves API ya existentes?
**Dashboard**
- ¿Qué resuelve hoy el Power BI de Pedro? (para **no duplicarlo** y definir qué aporta la plataforma).
## A confirmar antes de cerrar
- Fecha de inicio formal y calendario (Etapa 0 sem del 6-jul).
- Rol responsable de **confirmar cada emisión** de factura (control fiscal).
- Cadencia: demos viernes + reporte semanal de horas + facturación semanal.
- Que las sesiones se agendan **vía Erika** (intermediaria).
+69
View File
@@ -0,0 +1,69 @@
# Plan de actividades — Etapas 0 a 3
**Proyecto:** Plataforma de Automatización Financiera · Balam
> Fechas tentativas — el arranque depende de la entrega de accesos por Balam; se confirman en el kickoff. Las etapas 2 y 3 son estimadas y se afinan al cerrar el Discovery (Etapa 0). Total ~67 semanas (6-jul → 21-ago).
## Etapa 0
| Actividad | Inicio | Fin | Responsable | Apoyo de Balam |
|---|---|---|---|---|
| Sesión de arranque (kickoff) con el Ing. Noé | 01/07/2026 | 01/07/2026 | Johann | Sí — Noé y Erika (confirmada, 7am) |
| Sesión de Discovery: proceso actual de facturación y cobranza | 06/07/2026 | 07/07/2026 | Johann | Sí — quien lleva el proceso hoy |
| Entrega y validación de accesos (BIND, Azure, manual de marca) | 06/07/2026 | 07/07/2026 | Balam / Johann | Sí — API BIND, Azure, manual de marca |
| Validación técnica de la API de BIND con la cuenta real | 07/07/2026 | 08/07/2026 | Johann | Apoyo: llave de API |
| Configuración de infraestructura en Azure (+ staging) | 08/07/2026 | 09/07/2026 | Johann | Apoyo: accesos de Azure |
| Repositorio + CI/CD + gestión de secretos | 08/07/2026 | 09/07/2026 | Johann | — |
| Prototipo visual navegable (56 pantallas) | 08/07/2026 | 10/07/2026 | Johann | Apoyo: manual de marca |
| Sesión de validación del prototipo | 10/07/2026 | 10/07/2026 | Johann | Sí — Pedro y Araceli |
| Documento de hallazgos + ADRs + plan refinado | 10/07/2026 | 10/07/2026 | Johann | — |
**Entregable Etapa 0 (1822 h): prototipo navegable + infraestructura Azure + repo/CI-CD + documento de hallazgos.**
## Etapa 1
| Actividad | Inicio | Fin | Responsable | Apoyo de Balam |
|---|---|---|---|---|
| Backend base: autenticación, roles y bitácora de auditoría | 13/07/2026 | 15/07/2026 | Johann | — |
| Arquitectura multi-tenant (tenant_id + RLS) | 14/07/2026 | 15/07/2026 | Johann | — |
| Sesión de reglas de negocio con Arturo | 13/07/2026 | 13/07/2026 | Johann | Sí — Arturo |
| Cliente de la API de BIND (reintentos, OData, errores) | 15/07/2026 | 17/07/2026 | Johann | — |
| Capa de escritura controlada (cotización→factura) | 17/07/2026 | 22/07/2026 | Johann | — |
| Sincronización de clientes | 20/07/2026 | 21/07/2026 | Johann | — |
| Sincronización de facturas y cotizaciones | 21/07/2026 | 23/07/2026 | Johann | — |
| Modelo de facturas + migraciones + datos de prueba | 23/07/2026 | 24/07/2026 | Johann | — |
| Demostración semanal (viernes) + reporte de horas | 10/07/2026 | 24/07/2026 | Johann | Sí — Erika/Pedro |
**Entregable Etapa 1 (3239 h): plataforma base que sincroniza clientes y facturas de BIND, con capa de escritura controlada.**
## Etapa 2
| Actividad | Inicio | Fin | Responsable | Apoyo de Balam |
|---|---|---|---|---|
| Implementación productiva de las pantallas (Angular Material) + integración con el backend | 27/07/2026 | 31/07/2026 | Johann | — |
| Listado de facturas (filtros, búsqueda, paginación) + detalle con descarga PDF/XML | 28/07/2026 | 31/07/2026 | Johann | — |
| Catálogo de clientes + lista blanca configurable (ACUNTIA + Top 3) | 03/08/2026 | 04/08/2026 | Johann | — |
| Creación de cotizaciones + conversión a factura vía API de BIND | 31/07/2026 | 04/08/2026 | Johann | — |
| Emisión multimoneda MXN/USD + flujo con confirmación humana y dry-run | 04/08/2026 | 06/08/2026 | Johann | — |
| Tipo de cambio del DOF (proceso programado) | 05/08/2026 | 05/08/2026 | Johann | — |
| Exportación a CSV/XLSX + manual de usuario | 06/08/2026 | 07/08/2026 | Johann | — |
| Sesión de validación de facturación/emisión con Finanzas (pruebas en dry-run) | 07/08/2026 | 07/08/2026 | Johann | Sí — Finanzas/Arturo |
| Demostración semanal (viernes) | 31/07/2026 | 07/08/2026 | Johann | Sí — Erika/Pedro |
**Entregable Etapa 2 (3846 h): facturación operativa — consulta + emisión asistida MXN/USD con candados, catálogo + lista blanca, multimoneda y exportación.**
## Etapa 3
| Actividad | Inicio | Fin | Responsable | Apoyo de Balam |
|---|---|---|---|---|
| Sesión: alcance del tablero vs el Power BI existente (con Pedro) | 10/08/2026 | 10/08/2026 | Johann | Sí — Pedro |
| Módulo de cobranza: antigüedad de cartera (30/60/90) + por vencer y vencidas | 10/08/2026 | 12/08/2026 | Johann | — |
| Alertas internas configurables para Finanzas + aplicación de lista blanca | 12/08/2026 | 13/08/2026 | Johann | — |
| Tablero directivo de cuentas por cobrar | 13/08/2026 | 15/08/2026 | Johann | — |
| Reportes operativos configurables (CSV/XLSX/PDF) | 17/08/2026 | 18/08/2026 | Johann | — |
| Endurecimiento de seguridad + respaldos + plan de recuperación | 18/08/2026 | 19/08/2026 | Johann | — |
| Documentación técnica (README, runbook, ADRs) | 19/08/2026 | 20/08/2026 | Johann | — |
| Sesión de capacitación (grabada, 1.52 h) con Pedro y gerencia | 20/08/2026 | 20/08/2026 | Johann | Sí — Pedro/gerencia |
| Cierre formal del MVP + inicio del soporte de 2 semanas | 21/08/2026 | 21/08/2026 | Johann | — |
| Demostración semanal (viernes) | 14/08/2026 | 21/08/2026 | Johann | Sí — Erika/Pedro |
**Entregable Etapa 3 (2429 h): cobranza + alertas + tablero + reportes; cierre del MVP, documentación, capacitación y soporte de 2 semanas.**
Binary file not shown.
+487
View File
@@ -0,0 +1,487 @@
# Propuesta Comercial
## Plataforma de Automatización Financiera · Balam
| | |
|---|---|
| **Preparada para** | Noe Rocha (CTO) · Araceli Sánchez Jiménez (CEO/Operaciones) · Erika Chávez (PM) · Pedro Alberto Ayala Elizondo (Contacto técnico) |
| **Preparada por** | Johann Velazquez — Consultor de Software · Monterrey, N.L. |
| **Fecha** | Mayo 2026 |
| **Versión** | 1.1 — facturación BIND-first; ajustes comerciales acordados con Balam (8-jun-2026) |
| **Vigencia** | 30 días naturales a partir de la fecha de emisión |
---
## Resumen ejecutivo
Balam opera hoy un proceso financiero fragmentado: facturación, cobranza, conciliación y contabilidad viven en sistemas que no se comunican, y el trabajo de unirlos recae en personas. Cada traspaso manual introduce error, retrabajo y demoras de cobranza que erosionan la relación con los clientes estratégicos.
Esta propuesta plantea una **primera etapa enfocada**: una plataforma web que centraliza la facturación y las cuentas por cobrar sobre **BIND ERP**, el sistema ya confirmado como fuente de verdad. El MVP permite **emitir facturas en MXN y USD** desde la plataforma —creando la cotización y convirtiéndola en factura a través de la API de BIND, que conserva el timbrado CFDI— y entrega además visibilidad, dashboard, alertas y reglas de cobranza. Toda emisión opera con **confirmación humana y modo de simulación**, de modo que la prioridad #1 de Balam (facturación) se atiende sin asumir riesgos fiscales. La arquitectura queda preparada para incorporar conciliación bancaria, pagos en línea, contabilidad e integraciones adicionales conforme se validen.
En síntesis:
- **Alcance:** MVP de facturación (consulta y **emisión asistida** MXN/USD) y cobranza sobre la API de BIND ERP.
- **Plazo:** 6 a 7 semanas, a media dedicación.
- **Inversión:** **$67,200 $81,600 MXN** + IVA, bajo esquema de tiempo y materiales con tope por etapa.
- **Modelo:** transparencia total de horas, entregables verificables semana a semana, y código propiedad de Balam desde el primer día.
El enfoque responde directamente a la indicación del CTO de priorizar facturación e integrar únicamente BIND en esta primera fase, evitando comprometer alcance que dependa de integraciones aún no disponibles o no prioritarias.
---
## 1. Entendimiento del proyecto
### 1.1 El problema
Durante la sesión con el equipo directivo, la lectura del requerimiento fue clara: *"El proceso está tan desvinculado… pasa por varias manos… cada parte humana se está equivocando."* El reto no es de naturaleza técnica, sino operativa y económica. Cada traspaso manual entre nómina, facturación, cobranza, conciliación y contabilidad genera errores que se traducen en dinero perdido, retrabajo y deterioro en la relación con clientes clave.
### 1.2 Contexto que define el alcance
Tras la sesión de seguimiento y las actualizaciones del CTO del 25 de mayo, tres factores reordenan el alcance del MVP:
1. **BIND ERP cuenta con API oficial** (`api.bind.com.mx`, OData v3, 20,000 solicitudes/día). Esto permite una integración por API en tiempo cercano a real, superando el supuesto inicial de exportación manual de archivos.
2. **La dirección técnica priorizó la facturación.** Conforme a la indicación de considerar "únicamente BIND ERP en una primera etapa", funcionalidades como pagos en línea, conciliación bancaria, recordatorios automáticos a clientes y asientos contables se difieren de manera explícita a fases posteriores.
3. **BUK dispone de API**, confirmada por el equipo de Balam, aunque no constituye una prioridad inmediata. Queda contemplada en el roadmap sin condicionar el MVP.
### 1.3 Sistemas actuales
| Sistema | Función | Estatus en el MVP |
|---|---|---|
| **BIND ERP** | Facturación y contabilidad con PAC integrado | Integración principal (API confirmada) |
| **BUK** | Recursos humanos, contratos, nómina | Fuera de alcance (API disponible, fase posterior) |
| **Jira** | Gestión de proyectos y servicios | Fuera de alcance |
| **Banca (3 instituciones)** | 2 México + IBC Bank Texas, estados en PDF | Fuera de alcance |
### 1.4 Solución propuesta
Se propone una **capa de operaciones financieras sobre BIND ERP**, no un reemplazo. BIND conserva su rol como fuente de verdad para facturación, timbrado CFDI y contabilidad; la plataforma orquesta la visibilidad, las reglas de cobranza, el tablero directivo y la trazabilidad que hoy no existen de forma centralizada.
**Alcance funcional del MVP:**
- Integración con BIND ERP vía API: lectura primero y **escritura asistida** una vez validada en Discovery.
- **Emisión de facturas desde la plataforma:** creación de cotización y conversión a factura en MXN (con IVA) y USD (sin IVA, para clientes extranjeros) a través de la API de BIND, con timbrado a cargo del PAC de BIND, confirmación humana obligatoria y modo de simulación (*dry-run*) previo a cada emisión.
- Catálogo central de clientes con lista blanca configurable (ACUNTIA + Top 3).
- Vista unificada de facturación: estados, vencimientos, filtros y descarga de PDF/XML.
- Cobranza operativa: antigüedad de cartera (*aging*) y alertas internas para el equipo de Finanzas.
- Tablero financiero con indicadores de cuentas por cobrar, vencidas y próximas a vencer.
- Reportes exportables en CSV/XLSX.
- Operación multimoneda MXN y USD, con tipo de cambio del DOF capturado al momento de la facturación.
- Autenticación y control de acceso por roles (Finanzas, Dirección, Operaciones, Administración).
- Arquitectura preparada para multi-tenant y bitácora de auditoría universal.
**Visión integral (fases posteriores, cotizadas en el Anexo B):**
La arquitectura del MVP se diseña para cerrar progresivamente el ciclo financiero descrito por la dirección:
```
Jira (horas) → BUK (nómina) → Plataforma → Factura → Cobranza → Conciliación → Asiento
```
La incorporación posterior de pagos en línea, recordatorios automáticos, conciliación bancaria, asientos contables o BUK se realiza **sobre la misma base, sin reescribir lo construido**.
> **Sobre la automatización y los "agentes".** El MVP incluye automatización operativa **por reglas** (sincronización con BIND, alertas de vencimiento y reportes programados). Los **agentes con IA/LLM** (clasificación de transacciones, detección de anomalías) quedan para fase posterior, conforme el propio PRD los define como opcionales y a evaluar post-MVP.
### 1.5 Criterios de éxito de la primera etapa
- Emisión de facturas en MXN y USD desde la plataforma, con confirmación humana y sin incidentes fiscales.
- Visibilidad en tiempo cercano a real del estado de facturación y cuentas por cobrar, para Dirección y Finanzas.
- Reducción medible del trabajo manual de consulta y reporteo sobre BIND.
- Aplicación correcta de la lista blanca: ACUNTIA y Top 3 nunca reciben recordatorios automáticos masivos.
- Entrega del MVP funcional en el plazo comprometido, con alcance acotado y sin desviaciones.
- Control fiscal: ninguna factura se emite sin confirmación humana explícita; el timbrado CFDI permanece a cargo del PAC de BIND.
### 1.6 Escala operativa de referencia
- 45 colaboradores en nómina y 5 freelancers.
- Aproximadamente 50 facturas emitidas al mes.
- 3 instituciones bancarias (2 México + IBC Bank Texas) — fuera de esta etapa.
- 2 jurisdicciones fiscales (México y Texas, impuestos estándar en el MVP).
- Interlocutores operativos: Pedro (contacto técnico) y gerencia administrativa, con supervisión ejecutiva de Noe (CTO), Araceli (CEO) y Erika (PM).
---
## 2. Alcance detallado
> **Principio rector.** Un MVP entrega valor real en el menor tiempo posible con el alcance **mínimo viable**, no con todo lo deseable. Lo que no forma parte de esta etapa se documenta en el roadmap (Anexo B) con cotización indicativa.
> **Posicionamiento.** La plataforma es una capa de operaciones financieras sobre BIND ERP. BIND permanece como fuente de verdad para facturación, timbrado CFDI y contabilidad; la plataforma aporta visibilidad, reglas de cobranza, tablero y trazabilidad.
### Stack tecnológico propuesto
La selección tecnológica privilegia robustez empresarial, mantenibilidad a largo plazo y afinidad con Azure. El binomio **.NET + Azure** es una combinación nativa de Microsoft, lo que reduce fricción de despliegue, seguridad y operación para una aplicación financiera.
| Capa | Tecnología | Versión objetivo | Justificación |
|---|---|---|---|
| Frontend | **Angular + Angular Material** | Angular 21 · Material 21 | SPA escalable, fuertemente tipada y de estructura opinada; componentes de datos listos para las vistas de facturación y cobranza. |
| Backend | **C# / .NET (ASP.NET Core Web API)** | .NET 10 (LTS) · C# 14 | Tipado fuerte, madurez empresarial y desempeño; soporte de primera clase en Azure. |
| Acceso a datos | **Entity Framework Core** (proveedor Npgsql) | EF Core 10 · Npgsql 10 | Modelo tipado y migraciones versionadas, con control fino para multi-tenant y bitácora. |
| Base de datos | **Azure Database for PostgreSQL Flexible** | PostgreSQL 17 | Base productiva administrada (Azure SQL Database queda como alternativa nativa de .NET si Balam lo prefiere). |
| Infraestructura | **Azure App Service · Key Vault · Application Insights · Blob Storage** | runtime .NET 10 (Linux) | Cómputo administrado, gestión de secretos cifrada y observabilidad integradas. |
| Integración BIND | **Cliente .NET tipado** sobre la API OData de BIND | .NET 10 · HttpClient + Polly | Reintentos, manejo de límites de uso y mapeo de errores; contrato validado previamente en el sandbox de descubrimiento. |
| Control de acceso | **ASP.NET Core Identity + JWT**, autorización por roles | incluido en .NET 10 | Cuatro roles (Finanzas, Dirección, Operaciones, Administración). |
| Entrega continua | **GitHub + CI/CD** (GitHub Actions / Azure DevOps) | — | Despliegues controlados con validación automatizada. |
> **Sobre las versiones.** Las versiones indicadas son la referencia vigente a mayo de 2026; se fijan al inicio del proyecto sobre la rama estable/LTS de cada tecnología. Se prioriza **.NET 10** por ser versión de soporte extendido (LTS), apropiada para una aplicación financiera de largo plazo.
El proyecto se organiza en cuatro etapas secuenciales. Cada una concluye con un entregable demostrable y se factura conforme a horas reales (ver §3).
### Etapa 0 — Discovery, infraestructura y prototipo visual · *Semana 1 · 18 22 h*
**Objetivo.** Validar el comportamiento real de la API de BIND con la cuenta de Balam, preparar la infraestructura en Azure, dejar el repositorio operativo y **entregar un prototipo visual navegable** que alinee expectativas de producto antes de iniciar el desarrollo del backend.
**Entregables:**
- Validación de la API de BIND con cuenta real: recursos `Invoices`, `Payments`, `Customers`, `Products` y `Activities`; confirmación de los campos `saldo`, `vencimiento` y `estado`, y del comportamiento de paginación OData.
- Confirmación de los límites de uso (20,000 solicitudes/día) y definición de la estrategia de consumo.
- Infraestructura base en Azure: App Service, PostgreSQL Flexible, Storage, Key Vault y Application Insights; ambiente de staging preparado.
- Repositorio monorepo con CI/CD, gestión de secretos y guardrails de seguridad del flujo de trabajo asistido por IA.
- Documento de hallazgos, decisiones de arquitectura (ADRs) iniciales y plan refinado de las etapas siguientes.
- **Prototipo visual navegable** (con la identidad del manual de marca aplicada) de las 56 pantallas principales: inicio de sesión, tablero financiero, listado de facturas, detalle de factura, catálogo de clientes con lista blanca y configuración de alertas. Con datos de ejemplo y navegación real, para validar las decisiones de experiencia con Pedro y Araceli antes de construir.
> **Riesgo a validar en Discovery.** BIND documenta públicamente los recursos `Customers`, `Products` y `Activities`; `Invoices` y `Payments` son esperados, pero los nombres exactos de campos y la disponibilidad del saldo abierto por factura solo se confirman con la cuenta real. Se contemplan tres escenarios: **Plan A** (BIND expone el saldo directamente) y **Plan B** (el saldo se calcula como total suma de pagos) están cubiertos por el rango cotizado; el **Plan C** (interfaz propia para registro manual de pagos por parte de Finanzas, con un esfuerzo adicional estimado de 68 h **no incluidas en el rango**) se evaluaría y aprobaría al cierre de esta etapa antes de continuar.
### Etapa 1 — Plataforma base y sincronización con BIND · *Semanas 2-3 · 32 39 h*
**Objetivo.** Plataforma funcional que ingiere clientes y facturas de BIND vía API, los normaliza, mantiene su trazabilidad y habilita la capa de escritura controlada.
**Entregables:**
- Backend con autenticación, control de acceso por roles (Finanzas, Dirección, Operaciones, Administración) y bitácora de auditoría universal.
- Arquitectura multi-tenant a nivel de esquema (`tenant_id` + RLS), sin alta autoservicio.
- Cliente de la API de BIND con doble encabezado de autenticación, reintentos con backoff, manejo de límites de uso, constructor de consultas OData y mapeo de errores.
- **Capa de escritura controlada:** operaciones de creación de cotización y conversión a factura, envueltas en un mecanismo de simulación (*dry-run*), confirmación y feature flag, con registro en bitácora de toda llamada de escritura.
- Sincronización programada de clientes (normalización y deduplicación).
- Sincronización programada de facturas y cotizaciones (estado normalizado, captura de tipo de cambio y manejo de paginación OData).
- Modelo central de facturas con estados (Emitida, Vigente, Vencida, Pagada, Cancelada).
- Migraciones versionadas y datos de prueba.
> **Nota fiscal.** La plataforma **no timbra directamente**; el timbrado CFDI permanece en el PAC integrado de BIND. La plataforma orquesta la creación de cotizaciones y facturas mediante la API de BIND. Toda operación de escritura permanece desactivada (feature flag) hasta validar su comportamiento en Discovery, y ninguna emisión ocurre sin confirmación humana explícita.
### Etapa 2 — Facturación (consulta y emisión), catálogo y multimoneda · *Semanas 4-5 · 38 46 h*
**Objetivo.** Llevar a producción la interfaz validada en el prototipo, conectada al backend real, habilitando tanto la consulta como la **emisión asistida de facturas** en MXN y USD, con catálogo de clientes y soporte multimoneda.
**Entregables:**
- Implementación productiva de las pantallas validadas (componentes de Angular Material con la identidad ya aplicada en la Etapa 0).
- Integración del frontend con el backend (autenticación, ruteo, gestión de estado y caché).
- Listado de facturas con filtros (cliente, moneda, fecha, vencimiento, estatus), búsqueda, paginación y ordenamiento.
- Detalle de factura con descarga de PDF/XML desde BIND.
- **Creación de cotizaciones** desde la plataforma y **conversión a factura** vía API de BIND.
- **Emisión multimoneda:** MXN con IVA y USD sin IVA para clientes extranjeros, conforme a las reglas fiscales aplicables.
- **Flujo de emisión con confirmación humana obligatoria y modo de simulación (*dry-run*):** ninguna factura se emite sin aprobación explícita; toda operación de escritura queda registrada en bitácora.
- Catálogo de clientes con la bandera de lista blanca configurable (ACUNTIA + Top 3), editable por el rol de Administración.
- Tipo de cambio del DOF (proceso programado y captura al momento de la facturación) para la conversión MXN ↔ USD.
- Exportación de facturas y clientes a CSV/XLSX.
- Manual de usuario para los equipos de Finanzas y Dirección.
> **Fuera de esta etapa:** generación automática de facturas desde eventos de negocio (Jira → BUK → factura), soporte EUR e integración con BUK. El timbrado CFDI permanece a cargo del PAC de BIND.
### Etapa 3 — Cobranza, tablero, reportes y cierre · *Semanas 6-7 · 24 29 h*
**Objetivo.** Cerrar el flujo operativo con visibilidad para Dirección y realizar la entrega formal.
**Entregables:**
- Módulo de cobranza: antigüedad de cartera (30/60/90 días) y vistas de facturas por vencer y vencidas.
- Alertas internas configurables para Finanzas (5 días antes del vencimiento, facturas vencidas y registros con datos faltantes).
- Aplicación de la lista blanca: ACUNTIA y Top 3 nunca generan alertas automáticas de envío masivo.
- Tablero directivo con indicadores de cuentas por cobrar totales y por cliente, vencidas, próximas a vencer, monto pendiente por cliente y estado de alertas.
- Reporte operativo configurable (frecuencia, destinatarios y formato CSV/XLSX/PDF).
- Endurecimiento de seguridad y revisión de secretos.
- Respaldos automáticos y plan de recuperación documentado.
- Documentación técnica: README, runbook operativo y ADRs.
- Sesión de capacitación grabada (1.52 h) con Pedro y la gerencia administrativa.
- Transferencia y soporte posterior al lanzamiento de 2 semanas (corrección de defectos).
> **Fuera de esta etapa:** recordatorios automáticos a clientes, pagos en línea, conciliación bancaria, asientos contables automáticos e integración bancaria en vivo.
---
## 3. Modelo de colaboración
### 3.1 Esquema: tiempo y materiales con tope por etapa
El proyecto se desarrolla bajo un esquema de **horas reales con tarifa transparente**, no de precio cerrado. El alcance preciso de la integración con BIND —campos exactos, comportamiento de `Invoices` y `Payments`, manejo de saldos— solo se conoce al inspeccionar la API en vivo con la cuenta de Balam. Un precio fijo obligaría a incorporar un margen de contingencia que encarecería el proyecto de forma innecesaria.
A cambio de esta flexibilidad, el esquema ofrece controles concretos:
- **Rango estimado por etapa** (mínimomáximo de horas), acordado por escrito antes de iniciar cada una.
- **Tope por etapa:** de aproximarse al máximo del rango, el trabajo se detiene y se revisa el alcance con Balam antes de continuar. El rango opera como referencia de control, no como sugerencia.
- **Reporte semanal** de horas trabajadas con desglose por tarea.
- **Demostración semanal** del avance entregado.
### 3.2 Dedicación y plazo
La dedicación es de **media jornada (aproximadamente 20 h/semana**, con flexibilidad hasta 25 h en semanas de mayor carga). Sobre esa base, la primera etapa requiere **112 a 136 horas**, equivalentes a **6 7 semanas** de calendario.
### 3.3 Tarifa y facturación
- **Tarifa:** **$600 MXN por hora trabajada** + IVA, con comprobante fiscal CFDI 4.0. Aplica de igual forma a las fases posteriores (Anexo B) y a la bolsa de horas de soporte (§5).
- **Facturación:** por etapa, conforme se entrega cada una (con el anexo de desglose de horas por tarea). La Etapa 0 se factura al inicio del proyecto.
- **Pago:** a **30 días naturales** posteriores a la factura, mediante transferencia, conforme a la política de Balam.
- **Arranque sin anticipo:** conforme a la política de Balam, no se maneja anticipo. La **Etapa 0 (30 horas, $18,000 MXN + IVA)** se factura al inicio y su pago corre a 30 días, igual que los avances. El arranque queda condicionado a la **firma del contrato / orden de trabajo**, que formaliza el compromiso de ambas partes en sustitución del anticipo. Si Discovery revela bloqueadores que modifiquen el alcance de forma significativa, se replantea el plan antes de continuar.
### 3.4 Comunicación
- Reportes asíncronos 23 veces por semana (avance, siguientes pasos y bloqueadores).
- Demostración semanal (30 min, viernes) con Pedro y la gerencia administrativa.
- Sesión técnica quincenal (1 h) con Noe para decisiones de arquitectura y validación de reglas de negocio.
- Disponibilidad para reuniones urgentes con 24 h de anticipación, en horario laboral (9:0018:00, CST).
### 3.5 Metodología de desarrollo asistido por IA
El desarrollo se apoya en herramientas de asistencia por IA (Claude Code, de Anthropic) como acelerador de productividad. Por transparencia y cumplimiento, se hacen explícitos los siguientes compromisos:
- **Responsabilidad humana.** Todo el código entregado es revisado y validado por el proveedor. La IA es un asistente; la responsabilidad de cada línea integrada al repositorio es de Johann Velazquez.
- **Protección de datos.** Ningún dato real de Balam (clientes, montos, credenciales, XML reales) se comparte con modelos externos. El desarrollo se realiza con datos sintéticos derivados de la estructura, no del contenido. Las credenciales residen en gestores de secretos cifrados.
- **Cumplimiento.** La metodología se apega a la práctica habitual para el manejo de datos financieros: sin entrenamiento ni telemetría sobre datos del cliente, y con el código alojado en el repositorio de Balam desde el primer día.
- **Auditabilidad.** Las decisiones de arquitectura quedan registradas en ADRs versionados.
> El detalle técnico de esta metodología, así como la referencia a las políticas de Anthropic, puede ampliarse en una sesión específica o anexo a solicitud del equipo técnico de Balam.
---
## 4. Inversión
La inversión de la primera etapa se desglosa por entregable, conforme al esquema de tiempo y materiales:
| Etapa | Entregable principal | Rango (horas) | Inversión (MXN) |
|---|---|---|---|
| 0 | Discovery, infraestructura Azure y **prototipo visual navegable** | 18 22 | $10,800 $13,200 |
| 1 | Plataforma base, sincronización con BIND y capa de escritura | 32 39 | $19,200 $23,400 |
| 2 | Facturación (consulta y **emisión** MXN/USD), catálogo y multimoneda | 38 46 | $22,800 $27,600 |
| 3 | Cobranza, tablero, reportes y cierre | 24 29 | $14,400 $17,400 |
| **Total primera etapa** | | **112 136 h** | **$67,200 $81,600** |
> Cifras antes de IVA. Plazo estimado: **6 7 semanas** a media jornada (~20 h/semana, con flexibilidad hasta 25 h en semanas pico).
> El esquema de control por etapa busca que la inversión se concentre en la parte baja del rango; el margen superior cubre eventualidades del Discovery con BIND. Toda variación se comunica antes de incurrir en ella.
---
## 5. Soporte posterior al MVP
### 5.1 Garantía incluida
- **Defectos en funcionalidad entregada:** cobertura sin costo durante **45 días** posteriores a la entrega de cada etapa. El plazo de 45 días cubre el primer cierre mensual de operación, cuando suelen hacerse visibles los defectos en un sistema financiero.
- **Soporte posterior al lanzamiento:** **2 semanas** tras el cierre del MVP, para corrección de defectos (no nuevo alcance).
### 5.2 Bolsa de horas de soporte (opcional)
Concluida la garantía y el soporte incluidos, se ofrece una **bolsa de horas** para la operación y evolución incremental de la plataforma, contratable por bloques:
| Bloque | Horas | Inversión (sin IVA) |
|---|---|---|
| Pequeño | 10 h | $6,000 |
| Mediano *(recomendado)* | 20 h | $12,000 |
| Grande | 40 h | $24,000 |
**Vigencia:** las horas son **válidas por 12 meses** desde su contratación, **sin caducidad mensual** (se consumen al ritmo que Balam necesite). Tarifa $600 MXN/h + IVA.
**Cubre:** corrección de defectos fuera de garantía; ajustes menores y requerimientos pequeños; mantenimiento de dependencias y compatibilidad ante cambios de la API de BIND; atención a incidencias en 1 día hábil; y reporte de horas consumidas.
**No cubre:** desarrollo de los módulos diferidos (Anexo B); migraciones o cambios mayores de arquitectura; ni incidentes atribuibles a terceros (Azure, BIND, SAT).
---
## 6. Costos de servicios de terceros (a cargo de Balam)
Los siguientes son servicios de infraestructura externos, independientes de los honorarios del proveedor:
| Servicio | Configuración | Costo mensual (USD) |
|---|---|---|
| Azure App Service (Linux, Basic) | B1 (mínimo) → B2/B3 (recomendado); el proceso en segundo plano corre como WebJob en el mismo plan | $13 $51 |
| Azure Database for PostgreSQL Flexible | Burstable B1ms (mínimo) → B2s (recomendado) + 3264 GiB de almacenamiento y respaldo | $16 $65 |
| Azure Blob Storage (Hot, LRS) | PDF/XML y exportaciones (pocos GB) | $1 $5 |
| Azure Key Vault + Application Insights | Secretos (Standard) + observabilidad (5 GB/mes incluidos, luego por GB) | $0 $15 |
| Monitoreo de disponibilidad / Sentry *(opcional)* | Plan gratuito → Team | $0 $26 |
| PAC para CFDI | Ya incluido en BIND | Sin costo adicional |
| **Total mensual estimado** | | **$30 $160 USD/mes** |
> **Configuración esperada en producción** (App Service B2 + PostgreSQL B2s con respaldo + observabilidad básica): **≈ $90 $110 USD/mes**. El extremo bajo de la tabla corresponde a una configuración mínima; el alto, a una holgada con redundancia y monitoreo de pago.
> **Detalle de tarifas unitarias verificadas** (precios de lista *pay-as-you-go*, regiones de EE. UU. — East US / South Central US — referencia a mayo de 2026; la región Azure **México Central** puede variar ligeramente): App Service Linux **B1 ≈ $13.14**, **B2 ≈ $25.55**, **B3 ≈ $51.10**/mes · PostgreSQL Burstable **B1ms ≈ $12.41**, **B2s ≈ $49.64**/mes; almacenamiento ≈ **$0.1150.138/GiB-mes**, respaldo sobre lo provisionado ≈ **$0.095/GiB-mes** · Blob Hot LRS ≈ **$0.018/GB-mes** · Key Vault Standard **$0.03 / 10,000 operaciones** · Application Insights **5 GB/mes incluidos**, luego ≈ **$2.302.76/GB**.
> Los costos de Azure pueden optimizarse mediante **instancias reservadas** (13 años, hasta ~3040% de ahorro en cómputo), una vez validado el consumo real (36 meses posteriores al lanzamiento).
> En fases posteriores se incorporarían: Stripe (3.6% + $3 MXN por transacción con tarjeta; ~$3 MXN por SPEI), Claude API para el procesamiento de estados de cuenta en PDF ($5 $20 USD/mes), correo transaccional ($0 $20 USD/mes) y, opcionalmente, un agregador bancario como Belvo ($200 $500 USD/mes).
---
## 7. Supuestos y dependencias
El cumplimiento de las estimaciones depende de las siguientes condiciones. De no satisfacerse alguna, la etapa correspondiente se replantea de común acuerdo:
1. **Acceso productivo a la API de BIND**, con los permisos definidos —**lectura y escritura** (creación de cotización y conversión a factura)— y documentación oficial disponible, al inicio de la Etapa 0.
2. **Cuenta de Azure de Balam** (o autorización para crearla a su nombre) disponible al inicio de la Etapa 0.
3. **Manual de marca** entregado al inicio de la Etapa 0.
4. **Reglas de negocio finales** validadas en Discovery: lista blanca de clientes (ACUNTIA + Top 3 confirmados), ciclos de cobranza, días de anticipación de alertas y frecuencia de sincronización.
5. **Disponibilidad de dos interlocutores** (Pedro y la gerencia administrativa) con capacidad de resolver bloqueadores en menos de 48 horas.
6. **Producción como único ambiente disponible** (sin sandbox de BIND). La plataforma opera en modo de simulación cuando exista riesgo de afectar BIND, requiriendo confirmación explícita antes de cualquier escritura.
7. **Acuerdo de uso de la metodología asistida por IA**, con el compromiso de no entrenamiento sobre datos del cliente.
8. **El PAC integrado de BIND** atiende los volúmenes actuales y futuros sin costo adicional; el timbrado CFDI permanece a cargo de BIND, no de la plataforma.
9. **El comportamiento de escritura de la API de BIND** (creación de cotización, conversión a factura y timbrado) se valida en Discovery; la emisión productiva se habilita únicamente tras esa validación. Balam designa al rol responsable de confirmar cada emisión.
---
## 8. Entregables y propiedad
Más allá del código funcional, la entrega incluye:
- **Código fuente en el repositorio de Balam** (GitHub) desde el primer día; la propiedad intelectual es de Balam.
- **Documentación técnica viva** en el repositorio.
- **Pruebas automatizadas** para los flujos críticos (sincronización con BIND, lista blanca, multimoneda y control de acceso).
- **Pipeline de CI/CD** operativo, con despliegues controlados.
- **Respaldos automatizados** y plan de recuperación documentado.
- **Sesión de transferencia de conocimiento** grabada.
- **Soporte posterior al lanzamiento** de 2 semanas (corrección de defectos).
---
## 9. Fuera del alcance de esta etapa
- Rediseño visual integral o sistema de diseño propio (se emplea la la identidad del manual de marca de Balam; un diseño a la medida requeriría sumar un diseñador).
- Adquisición de licencias de terceros (PAC, agregadores, hosting Azure).
- Gestión del cambio organizacional más allá de la sesión de capacitación.
- Módulos diferidos: pagos en línea, recordatorios automáticos a clientes, conciliación bancaria, asientos contables, BUK e IA — cotizados de forma indicativa en el Anexo B.
- Integraciones no listadas (Book, CRM, etc.), cotizables por separado.
- Adaptaciones derivadas de cambios fiscales del SAT o modificaciones disruptivas en la API de BIND que impliquen retrabajo mayor (atendibles mediante la bolsa de horas de soporte, §5.2).
---
## 10. Garantía y condiciones generales
- **Garantía de defectos:** cobertura sin costo durante 45 días posteriores a la entrega de cada etapa.
- **Control de cambios:** toda funcionalidad fuera del alcance acordado se documenta como solicitud de cambio (Change Request), se estima y se aprueba por escrito antes de ejecutarse.
- **Propiedad intelectual:** el código es propiedad de Balam desde el primer commit. El proveedor conserva el derecho de referir el proyecto en su portafolio sin divulgar información confidencial.
- **Responsabilidad del código asistido por IA:** todo defecto queda cubierto por la misma garantía. La responsabilidad final del código corresponde al proveedor, con independencia de las herramientas utilizadas.
> Las condiciones contractuales detalladas (confidencialidad, límite de responsabilidad, jurisdicción y demás cláusulas) se formalizan en el contrato de prestación de servicios previo al inicio.
---
## 11. Próximos pasos
1. Sesión de revisión de esta propuesta (1 h) para resolver dudas y ajustar lo que corresponda.
2. **Firma del contrato / orden de trabajo** (prestación de servicios y confidencialidad) — formaliza el compromiso para arrancar sin anticipo.
3. **Factura de la Etapa 0** (30 horas, **$18,000 MXN** + IVA), con pago a 30 días, para iniciar Discovery.
4. Arranque del proyecto, con entrega del MVP en **6 7 semanas**.
---
**Johann Velazquez** · Consultor de Software
Monterrey, Nuevo León
---
---
## Anexo A — Trazabilidad PRD ↔ MVP
Este anexo relaciona cada funcionalidad y requerimiento del **PRD de Balam** con el alcance comprometido en la primera etapa, como referencia para alinear expectativas sobre lo incluido, lo parcial y lo diferido.
Leyenda: ✅ Cubierto · ⚠️ Parcial · ❌ Diferido a fase posterior (Anexo B)
### A.1 Funcionalidades (PRD §3.1)
#### Facturación
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Generación automatizada de facturas | ⚠️ | **Incluida la creación asistida:** cotización → factura desde la plataforma vía API de BIND, con confirmación humana; el timbrado lo realiza el PAC de BIND. La generación **automática desde eventos** (Jira→BUK→Factura) depende de APIs no disponibles hoy — fase posterior. |
| Multimoneda para clientes internacionales (USD sin IVA) | ✅ | **Emisión en MXN (con IVA) y USD (sin IVA)** con tipo de cambio del DOF capturado al momento de facturación. **EUR queda fuera del MVP.** |
| Descarga automática de facturas / reporte | ✅ | Listado con filtros, búsqueda, detalle con PDF/XML y exportación a Excel. |
#### Cobranza
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Registro automático de pagos | ❌ | Diferido (vía webhook de pagos en línea). En esta etapa los pagos se reflejan según lo que reporta `Payments` de BIND. |
| Identificación de pagos por cliente | ⚠️ | Vista de saldos por cliente con base en datos de BIND; conciliación avanzada en fase posterior. |
| Seguimiento de CxC con lista blanca ACUNTIA y Top 3 | ✅ | Lista blanca configurable, antigüedad de cartera y alertas internas para Finanzas. **Recordatorios automáticos a clientes → fase posterior.** |
#### Conciliación bancaria
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Conciliación automática pagos vs. facturas | ❌ | Diferido — coincidencia exacta, por alias y cola de revisión humana. |
| Integración con estados de cuenta (3 bancos, incl. IBC Texas) | ❌ | Diferido — carga de PDF y extracción estructurada. |
| Identificación de discrepancias | ❌ | Diferido. |
#### Contabilidad
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Generación automática de asientos contables | ❌ | Diferido. Depende de la capacidad de escritura validada de la API de BIND. |
| Integración con sistema contable existente | ❌ | Diferido — exportación en formato BIND para carga manual del contador. |
#### Reportes y alertas
| Funcionalidad PRD | Cobertura | Detalle |
|---|---|---|
| Tablero de estado financiero | ✅ | Indicadores de CxC totales, por cliente, vencidas y próximas a vencer. |
| Alerta — pagos pendientes | ✅ | Alertas internas para Finanzas. |
| Alerta — errores en conciliación | ❌ | Diferido (sin conciliación en el MVP). |
| Alerta — facturas no cobradas | ✅ | Alertas internas; envío automático a clientes → fase posterior. |
### A.2 Requerimientos funcionales (PRD §6)
| ID | Descripción | Cobertura | Notas |
|---|---|---|---|
| RF-01 | Generar facturas automáticamente desde eventos | ⚠️ | Creación manual (cotización→factura) incluida; la generación automática desde eventos requiere Jira/BUK — fase posterior |
| RF-02 | Multimoneda USD/EUR/MXN | ⚠️ | Emisión MXN + USD en el MVP; EUR en fase posterior |
| RF-03 | Integración con sistemas existentes (BUK / nómina) | ❌ | API de BUK confirmada, no prioritaria |
| RF-04 | Registrar pagos desde fuentes bancarias | ❌ | Fase posterior |
| RF-05 | Asociar pagos a facturas | ⚠️ | Con base en `Payments` de BIND; conciliación avanzada después |
| RF-06 | Identificar pagos parciales y completos | ⚠️ | Según lo que reporte BIND |
| RF-07 | Conciliar transacciones bancarias | ❌ | Fase posterior |
| RF-08 | Detectar discrepancias | ❌ | Fase posterior |
| RF-09 | Reportes de conciliación | ❌ | Fase posterior |
| RF-10 | Asientos contables automáticos | ❌ | Fase posterior |
| RF-11 | Integración con sistema contable | ❌ | Fase posterior (exportación en formato BIND) |
| RF-12 | Tablero financiero en tiempo real | ✅ | Incluido |
| RF-13 | Alertas configurables | ⚠️ | Internas en el MVP; a clientes en fase posterior |
| RF-14 | Reportes exportables | ✅ | Incluido |
### A.3 Requerimientos no funcionales (PRD §7)
| ID | Descripción | Cobertura | Notas |
|---|---|---|---|
| RNF-01 | Arquitectura modular y escalable | ✅ | Multi-tenant a nivel de esquema (`tenant_id` + RLS) |
| RNF-02 | Alta disponibilidad | ⚠️ | MVP en una sola región de Azure; HA multi-región → fase posterior |
| RNF-03 | Seguridad de datos financieros | ✅ | Secretos en Key Vault, control de acceso, bitácora y endurecimiento |
| RNF-04 | Cumplimiento fiscal (México y Texas) | ⚠️ | Impuestos estándar en el MVP; cumplimiento avanzado Texas → fase posterior |
| RNF-05 | Integración con APIs externas | ✅ | BIND API y DOF para tipo de cambio |
| RNF-06 | Trazabilidad completa de operaciones | ✅ | Bitácora de auditoría universal |
### A.4 Diferidos — resumen
**Bloqueado por terceros o sin prioridad actual:** generación automática de facturas (Jira→BUK→Factura); integración bancaria en vivo (Belvo/Plaid/IBC Texas); integración con BUK; integración con Jira.
**Diferido por decisión de alcance:** pagos en línea; recordatorios automáticos a clientes; conciliación bancaria; asientos contables automáticos; EUR; flujo de efectivo proyectado; detección avanzada de anomalías; portal de cliente; multi-tenant comercial; cumplimiento avanzado Texas.
---
## Anexo B — Cotización indicativa de fases posteriores
Los siguientes son **rangos indicativos**, no compromisos contractuales. Se reconfirman mediante su propio Discovery al activar cada módulo, con datos reales de uso del MVP. La tarifa de $600 MXN/h y el esquema de tiempo y materiales con tope por etapa se mantienen.
### B.1 Módulos cotizados
| Módulo | Horas | Inversión (MXN) |
|---|---|---|
| Recordatorios automáticos por correo + editor de plantillas + bitácora de comunicaciones | 12 16 | $7,200 $9,600 |
| Pago en línea (Stripe Checkout) + webhook de conciliación + tarjeta y SPEI | 14 18 | $8,400 $10,800 |
| Conciliación bancaria por PDF (3 bancos, incl. IBC Texas) + extracción asistida + motor de coincidencia + cola de revisión | 28 36 | $16,800 $21,600 |
| Exportación de pagos conciliados y movimientos en formato BIND | 8 12 | $4,800 $7,200 |
| Integración con BUK (lectura de nómina y colaboradores) | 18 24 | $10,800 $14,400 |
| **Subtotal** | **80 106 h** | **$48,000 $63,600** |
> El monto de esta fase es comparable al del MVP porque incorpora **cinco módulos nuevos completos**, cada uno con su propio diseño, integración y pruebas. No se trata de extensiones menores, sino de capacidades adicionales sobre la base ya construida.
### B.2 Roadmap posterior (sin cotización)
Se cotiza al cierre de la fase anterior, priorizando con datos reales de uso: integración bancaria en vivo (Belvo, Plaid); conector con BIND para asientos contables automáticos; integración con Jira; flujo de efectivo proyectado (30/60/90 días); gestión cambiaria automática; pagos recurrentes; detección avanzada de anomalías; soporte EUR; clasificación asistida por IA; portal de cliente; multi-tenant comercial; aplicación móvil para tickets; y cumplimiento avanzado para Texas.
### B.3 Activación
Al cierre del MVP se realiza una sesión de priorización (1 h) y se formaliza una orden de trabajo por módulo seleccionado. La tarifa, el esquema (facturación por etapa con pago a 30 días, sin anticipo), la comunicación y la garantía aplican en los mismos términos que en la primera etapa.
+673
View File
@@ -0,0 +1,673 @@
<!DOCTYPE html>
<!-- Propuesta Comercial Balam · v1.1 — render del design system proposal-pdf.
Contenido fiel a 00 - PROPUESTA-COMERCIAL.md. Build: python .claude/skills/proposal-pdf/scripts/build_pdf.py propuesta/pdf.config.json -->
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="author" content="Johann Velazquez">
<title>Propuesta Comercial — Plataforma de Automatización Financiera · Balam</title>
<style>
/* ===========================================================================
FUENTES DEL DISEÑO — embebidas, NO dependen de fuentes del sistema.
Los .ttf viven en ./fonts/ (junto a este HTML). Mantén esta carpeta al lado
del HTML al moverlo/copiarlo: sin ella, el PDF cae a Cambria/Calibri y pierde
el "look" del esperado. No hace falta editar este bloque.
=========================================================================== */
@font-face{ font-family:"Caladea"; font-style:normal; font-weight:400; src:url("fonts/Caladea-Regular.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:normal; font-weight:700; src:url("fonts/Caladea-Bold.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:italic; font-weight:400; src:url("fonts/Caladea-Italic.ttf") format("truetype"); }
@font-face{ font-family:"Caladea"; font-style:italic; font-weight:700; src:url("fonts/Caladea-BoldItalic.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:normal; font-weight:400; src:url("fonts/Carlito-Regular.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:normal; font-weight:700; src:url("fonts/Carlito-Bold.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:italic; font-weight:400; src:url("fonts/Carlito-Italic.ttf") format("truetype"); }
@font-face{ font-family:"Carlito"; font-style:italic; font-weight:700; src:url("fonts/Carlito-BoldItalic.ttf") format("truetype"); }
@font-face{ font-family:"DejaVu Sans Mono"; font-style:normal; font-weight:400; src:url("fonts/DejaVuSansMono.ttf") format("truetype"); }
:root{
--ink:#1C2B39; /* navy profundo — texto principal + portada */
--ink-soft:#33424F; /* texto secundario */
--muted:#6B7682; /* leyendas, footers */
--accent:#C0892F; /* dorado/ámbar Balam — único acento */
--accent-d:#9A6A1C; /* dorado oscuro para énfasis sobre tinte */
--accent-tint:#F6ECD7; /* relleno dorado tenue */
--cream:#F4EFEA; /* relleno cálido de panel */
--line:#D9DEE3; /* hairlines */
--serif:"Caladea", "Cambria", Georgia, "Times New Roman", serif;
--sans:"Carlito", "Calibri", "Helvetica Neue", Arial, sans-serif;
--mono:"DejaVu Sans Mono", "SFMono-Regular", Consolas, monospace;
}
@page{ size:Letter; margin:15mm 16mm 18mm 16mm; }
@page :first{ margin:0; } /* portada full-bleed */
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
body{
font-family:var(--sans); color:var(--ink-soft);
font-size:10pt; line-height:1.5;
-webkit-print-color-adjust:exact; print-color-adjust:exact;
}
p{ margin:0 0 7pt; }
strong,b{ color:var(--ink); font-weight:700; }
em{ font-style:italic; }
code{
font-family:var(--mono); font-size:8.6pt;
background:var(--cream); padding:.5pt 3pt; border-radius:2pt; color:var(--ink);
}
a{ color:var(--accent-d); text-decoration:none; }
/* ---------- PORTADA ---------- */
.cover{
width:216mm; min-height:279mm; background:var(--ink); color:#EAEDF0;
padding:30mm 26mm 24mm; display:flex; flex-direction:column;
position:relative; overflow:hidden;
}
.cover::after{
content:""; position:absolute; right:-60mm; top:-60mm;
width:150mm; height:150mm; border-radius:50%;
background:radial-gradient(circle at center, rgba(192,137,47,.20), rgba(192,137,47,0) 70%);
}
.cover .top,.cover .mid,.cover .meta{ position:relative; z-index:2; }
.cover .logo{ margin-bottom:16pt; }
.cover .logo .txt{ font-family:var(--serif); font-weight:700; font-size:15pt; color:#fff; letter-spacing:.03em; }
.cover .logo .txt b{ color:var(--accent); }
.cover .rule{ width:46pt; height:3pt; background:var(--accent); margin-bottom:14pt; }
.cover .eyebrow{
font-size:9pt; letter-spacing:.32em; text-transform:uppercase; color:#9FB0BF; font-weight:700;
}
.cover .mid{ margin-top:auto; margin-bottom:auto; padding:18mm 0; }
.cover h1{
font-family:var(--serif); font-weight:700; font-size:37pt; line-height:1.06;
letter-spacing:-.01em; margin:0; color:#FFFFFF; max-width:150mm;
}
.cover .client{ margin-top:16pt; font-family:var(--sans); font-size:12.5pt; color:#C9D1D8; }
.cover .client b{ color:var(--accent); font-weight:700; }
.meta-grid{ display:grid; grid-template-columns:34mm 1fr; gap:6pt 10pt; margin:0; font-size:9.2pt; }
.meta-grid dt{ color:#8A98A5; text-transform:uppercase; letter-spacing:.12em; font-size:7.6pt; padding-top:1.5pt; }
.meta-grid dd{ margin:0; color:#D7DDE2; }
.cover .confidential{
margin-top:18pt; display:flex; justify-content:space-between;
font-size:8pt; letter-spacing:.18em; text-transform:uppercase; color:#7E8C99;
}
/* ---------- TOC ---------- */
.toc{ break-before:page; padding-top:6mm; }
.kick{ font-size:8.5pt; letter-spacing:.28em; text-transform:uppercase; color:var(--accent-d); font-weight:700; }
.toc h2{ font-family:var(--serif); font-size:26pt; font-weight:700; color:var(--ink); margin:2pt 0 4pt; }
.toc .bar{ width:40pt; height:3pt; background:var(--accent); margin:6pt 0 16pt; }
.toc-row{ display:flex; align-items:baseline; gap:8pt; padding:6.2pt 0; border-bottom:.6pt solid var(--line); font-size:10.5pt; }
.toc-row .n{ width:22pt; color:var(--accent-d); font-weight:700; font-variant-numeric:tabular-nums; }
.toc-row .t{ color:var(--ink); }
.toc-row.section .t{ font-weight:700; }
.toc-row .dots{ flex:1; border-bottom:1pt dotted var(--line); transform:translateY(-3pt); }
.toc-row .pg{ color:var(--muted); font-variant-numeric:tabular-nums; }
/* ---------- SECCIONES ---------- */
.content{ break-before:page; }
.sec{ margin-bottom:14pt; }
.sec-head{ display:flex; gap:12pt; align-items:flex-start; border-bottom:1.4pt solid var(--ink); padding-bottom:6pt; margin:0 0 11pt; }
.sec-head .sec-num{ font-family:var(--serif); font-size:30pt; font-weight:700; line-height:.9; color:var(--accent); min-width:42pt; }
.sec-head h2{ font-family:var(--serif); font-size:19pt; font-weight:700; color:var(--ink); margin:4pt 0 0; }
.sec-head .kick{ display:block; margin-bottom:2pt; }
.sec-head.no-num h2{ margin-top:0; }
h3{ font-family:var(--sans); font-size:11.5pt; font-weight:700; color:var(--ink); margin:13pt 0 5pt; }
h3 .sn{ color:var(--accent-d); font-weight:700; margin-right:7pt; }
h4{ font-family:var(--sans); font-size:9.5pt; font-weight:700; color:var(--ink-soft); text-transform:uppercase; letter-spacing:.06em; margin:10pt 0 3pt; }
p.lede{ font-size:11pt; color:var(--ink-soft); }
p.drop::first-letter{
font-family:var(--serif); font-size:34pt; font-weight:700; color:var(--accent);
float:left; line-height:.82; padding:2pt 6pt 0 0;
}
p.mini-label{ font-size:8pt; letter-spacing:.12em; text-transform:uppercase; color:var(--accent-d); font-weight:700; margin:9pt 0 1pt; }
ul,ol{ margin:4pt 0 8pt; padding-left:16pt; }
li{ margin:0 0 3.5pt; padding-left:2pt; }
li::marker{ color:var(--accent); }
/* ---------- TABLAS ---------- */
table{ width:100%; border-collapse:collapse; margin:10pt 0; font-size:9.2pt; }
thead th{
text-align:left; font-size:7.8pt; letter-spacing:.07em; text-transform:uppercase;
color:#FFFFFF; background:var(--ink); padding:5pt 8pt; font-weight:700;
}
thead th.num,thead th.ctr{ text-align:right; }
thead th.ctr{ text-align:center; }
tbody td{ padding:5.5pt 8pt; border-bottom:.6pt solid var(--line); vertical-align:top; }
tbody tr:nth-child(even) td{ background:#FBFAF8; }
td.k{ color:var(--ink); font-weight:600; }
.num,th.num{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
tr.total td{ font-weight:700; color:var(--ink); background:var(--cream); border-top:1.2pt solid var(--ink); border-bottom:1.2pt solid var(--ink); }
tr.sub-total td{ font-weight:700; color:var(--ink); background:var(--accent-tint); border-top:1pt solid var(--accent); }
td.cov{ font-size:11pt; text-align:center; line-height:1; }
table.compact tbody td{ padding:4pt 8pt; }
table.compact{ font-size:8.8pt; }
/* ---------- CALLOUTS ---------- */
.callout{
background:var(--accent-tint); border-left:3pt solid var(--accent);
padding:9pt 12pt; margin:10pt 0; font-size:9.4pt; color:var(--ink-soft); border-radius:0 3pt 3pt 0;
}
.callout strong,.callout b{ color:var(--accent-d); }
.callout.cool{ background:var(--cream); border-left-color:var(--ink); }
.callout.cool strong,.callout.cool b{ color:var(--ink); }
/* ---------- FLOW / PIPELINE ---------- */
.flow{ display:flex; flex-wrap:wrap; align-items:center; gap:5pt; margin:10pt 0; }
.flow .chip{
background:#fff; border:1pt solid var(--line); border-radius:4pt;
padding:4pt 9pt; font-size:8.6pt; font-weight:600; color:var(--ink-soft); white-space:nowrap;
}
.flow .chip.accent{ background:var(--accent); border-color:var(--accent); color:#fff; }
.flow .arrow{ color:var(--accent); font-weight:700; }
/* ---------- STATS ---------- */
.synthesis{ display:flex; gap:10pt; margin:12pt 0; }
.synthesis .stat{ flex:1; background:var(--ink); color:#EAEDF0; border-radius:5pt; padding:11pt 13pt; }
.synthesis .stat .big{ font-family:var(--serif); font-size:21pt; font-weight:700; color:#fff; line-height:1; }
.synthesis .stat .lab{ font-size:8pt; letter-spacing:.08em; text-transform:uppercase; color:#9FB0BF; margin-top:4pt; }
/* ---------- ETAPA ---------- */
.etapa{ border:1pt solid var(--line); border-left:3pt solid var(--accent); border-radius:0 4pt 4pt 0; padding:9pt 12pt; margin:8pt 0; }
.etapa-h{ font-weight:700; color:var(--ink); font-size:10pt; margin-bottom:3pt; }
.etapa-h .tag{ float:right; font-size:8pt; color:var(--muted); font-weight:600; }
/* ---------- SIGNOFF ---------- */
.signoff{ margin-top:18pt; padding-top:10pt; border-top:1.4pt solid var(--ink); }
.signoff .nm{ font-family:var(--serif); font-size:14pt; font-weight:700; color:var(--ink); }
.signoff .rl{ font-size:8.5pt; color:var(--muted); letter-spacing:.04em; }
/* ---------- PRINT SAFETY ---------- */
h2,h3,h4,.sec-head{ break-after:avoid; }
thead{ display:table-header-group; }
tfoot{ display:table-footer-group; }
tr{ break-inside:avoid; }
.callout,.synthesis,.synthesis .stat,.etapa,.flow,.signoff,figure,img{ break-inside:avoid; }
.keep,.no-break{ break-inside:avoid; }
.break{ break-before:page; }
</style>
</head>
<body>
<!-- ============================= PORTADA ============================= -->
<section class="cover">
<div class="top">
<div class="logo"><span class="txt"><b>BALAM</b> · Talento Estratégico</span></div>
<div class="rule"></div>
<div class="eyebrow">Propuesta Comercial</div>
</div>
<div class="mid">
<h1>Plataforma de Automatización Financiera</h1>
<div class="client">Preparada para <b>Balam</b></div>
</div>
<div class="meta">
<dl class="meta-grid">
<dt>Preparada para</dt>
<dd>Noe Rocha (CTO) · Araceli Sánchez Jiménez (CEO/Operaciones) · Erika Chávez (PM) · Pedro Alberto Ayala Elizondo (Contacto técnico)</dd>
<dt>Preparada por</dt>
<dd>Johann Velazquez — Consultor de Software · Monterrey, N.L.</dd>
<dt>Fecha</dt>
<dd>Mayo 2026</dd>
<dt>Versión</dt>
<dd>1.1 — facturación BIND-first; ajustes comerciales acordados con Balam (8-jun-2026)</dd>
<dt>Vigencia</dt>
<dd>30 días naturales a partir de la fecha de emisión</dd>
</dl>
<div class="confidential">
<span>Documento confidencial</span>
<span>Vigencia 30 días</span>
</div>
</div>
</section>
<!-- ============================= TOC ============================= -->
<section class="toc">
<div class="kick">Contenido</div>
<h2>Índice</h2>
<div class="bar"></div>
<div class="toc-row"><span class="n"></span><span class="t">Resumen ejecutivo</span><span class="dots"></span><span class="pg">{{PG_execsum}}</span></div>
<div class="toc-row section"><span class="n">01</span><span class="t">Entendimiento del proyecto</span><span class="dots"></span><span class="pg">{{PG_1}}</span></div>
<div class="toc-row section"><span class="n">02</span><span class="t">Alcance detallado</span><span class="dots"></span><span class="pg">{{PG_2}}</span></div>
<div class="toc-row section"><span class="n">03</span><span class="t">Modelo de colaboración</span><span class="dots"></span><span class="pg">{{PG_3}}</span></div>
<div class="toc-row section"><span class="n">04</span><span class="t">Inversión</span><span class="dots"></span><span class="pg">{{PG_4}}</span></div>
<div class="toc-row section"><span class="n">05</span><span class="t">Soporte posterior al MVP</span><span class="dots"></span><span class="pg">{{PG_5}}</span></div>
<div class="toc-row section"><span class="n">06</span><span class="t">Costos de servicios de terceros</span><span class="dots"></span><span class="pg">{{PG_6}}</span></div>
<div class="toc-row section"><span class="n">07</span><span class="t">Supuestos y dependencias</span><span class="dots"></span><span class="pg">{{PG_7}}</span></div>
<div class="toc-row section"><span class="n">08</span><span class="t">Entregables y propiedad</span><span class="dots"></span><span class="pg">{{PG_8}}</span></div>
<div class="toc-row section"><span class="n">09</span><span class="t">Fuera del alcance de esta etapa</span><span class="dots"></span><span class="pg">{{PG_9}}</span></div>
<div class="toc-row section"><span class="n">10</span><span class="t">Garantía y condiciones generales</span><span class="dots"></span><span class="pg">{{PG_10}}</span></div>
<div class="toc-row section"><span class="n">11</span><span class="t">Próximos pasos</span><span class="dots"></span><span class="pg">{{PG_11}}</span></div>
<div class="toc-row section"><span class="n">A</span><span class="t">Anexo A — Trazabilidad PRD ↔ MVP</span><span class="dots"></span><span class="pg">{{PG_anexoA}}</span></div>
<div class="toc-row section"><span class="n">B</span><span class="t">Anexo B — Cotización indicativa de fases posteriores</span><span class="dots"></span><span class="pg">{{PG_anexoB}}</span></div>
</section>
<!-- ============================= CUERPO ============================= -->
<main class="content">
<!-- ===== Resumen ejecutivo ===== -->
<section class="sec">
<div class="sec-head no-num"><div><h2>Resumen ejecutivo</h2></div></div>
<p class="drop">Balam opera hoy un proceso financiero fragmentado: facturación, cobranza, conciliación y contabilidad viven en sistemas que no se comunican, y el trabajo de unirlos recae en personas. Cada traspaso manual introduce error, retrabajo y demoras de cobranza que erosionan la relación con los clientes estratégicos.</p>
<p>Esta propuesta plantea una <strong>primera etapa enfocada</strong>: una plataforma web que centraliza la facturación y las cuentas por cobrar sobre <strong>BIND ERP</strong>, el sistema ya confirmado como fuente de verdad. El MVP permite <strong>emitir facturas en MXN y USD</strong> desde la plataforma —creando la cotización y convirtiéndola en factura a través de la API de BIND, que conserva el timbrado CFDI— y entrega además visibilidad, dashboard, alertas y reglas de cobranza. Toda emisión opera con <strong>confirmación humana y modo de simulación</strong>, de modo que la prioridad #1 de Balam (facturación) se atiende sin asumir riesgos fiscales. La arquitectura queda preparada para incorporar conciliación bancaria, pagos en línea, contabilidad e integraciones adicionales conforme se validen.</p>
<div class="synthesis">
<div class="stat"><div class="big">67</div><div class="lab">Semanas</div></div>
<div class="stat"><div class="big">112136</div><div class="lab">Horas (T&amp;M)</div></div>
<div class="stat"><div class="big">$67.2K</div><div class="lab">a $81.6K&nbsp;+&nbsp;IVA</div></div>
</div>
<p class="mini-label">En síntesis</p>
<ul>
<li><strong>Alcance:</strong> MVP de facturación (consulta y <strong>emisión asistida</strong> MXN/USD) y cobranza sobre la API de BIND ERP.</li>
<li><strong>Plazo:</strong> 6 a 7 semanas, a media dedicación.</li>
<li><strong>Inversión:</strong> <strong>$67,200 $81,600 MXN</strong> + IVA, bajo esquema de tiempo y materiales con tope por etapa.</li>
<li><strong>Modelo:</strong> transparencia total de horas, entregables verificables semana a semana, y código propiedad de Balam desde el primer día.</li>
</ul>
<p>El enfoque responde directamente a la indicación del CTO de priorizar facturación e integrar únicamente BIND en esta primera fase, evitando comprometer alcance que dependa de integraciones aún no disponibles o no prioritarias.</p>
</section>
<!-- ===== 1. Entendimiento ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">01</div><div><h2>Entendimiento del proyecto</h2></div></div>
<p class="lede">Durante la sesión con el equipo directivo, la lectura del requerimiento fue clara y orientó por completo el alcance de esta primera etapa.</p>
<h3><span class="sn">1.1</span>El problema</h3>
<p><em>"El proceso está tan desvinculado… pasa por varias manos… cada parte humana se está equivocando."</em> El reto no es de naturaleza técnica, sino operativa y económica. Cada traspaso manual entre nómina, facturación, cobranza, conciliación y contabilidad genera errores que se traducen en dinero perdido, retrabajo y deterioro en la relación con clientes clave.</p>
<h3><span class="sn">1.2</span>Contexto que define el alcance</h3>
<p>Tras la sesión de seguimiento y las actualizaciones del CTO del 25 de mayo, tres factores reordenan el alcance del MVP:</p>
<ol>
<li><strong>BIND ERP cuenta con API oficial</strong> (<code>api.bind.com.mx</code>, OData v3, 20,000 solicitudes/día). Esto permite una integración por API en tiempo cercano a real, superando el supuesto inicial de exportación manual de archivos.</li>
<li><strong>La dirección técnica priorizó la facturación.</strong> Conforme a la indicación de considerar "únicamente BIND ERP en una primera etapa", funcionalidades como pagos en línea, conciliación bancaria, recordatorios automáticos a clientes y asientos contables se difieren de manera explícita a fases posteriores.</li>
<li><strong>BUK dispone de API</strong>, confirmada por el equipo de Balam, aunque no constituye una prioridad inmediata. Queda contemplada en el roadmap sin condicionar el MVP.</li>
</ol>
<h3><span class="sn">1.3</span>Sistemas actuales</h3>
<table>
<thead><tr><th>Sistema</th><th>Función</th><th>Estatus en el MVP</th></tr></thead>
<tbody>
<tr><td class="k">BIND ERP</td><td>Facturación y contabilidad con PAC integrado</td><td>Integración principal (API confirmada)</td></tr>
<tr><td class="k">BUK</td><td>Recursos humanos, contratos, nómina</td><td>Fuera de alcance (API disponible, fase posterior)</td></tr>
<tr><td class="k">Jira</td><td>Gestión de proyectos y servicios</td><td>Fuera de alcance</td></tr>
<tr><td class="k">Banca (3 instituciones)</td><td>2 México + IBC Bank Texas, estados en PDF</td><td>Fuera de alcance</td></tr>
</tbody>
</table>
<h3><span class="sn">1.4</span>Solución propuesta</h3>
<p>Se propone una <strong>capa de operaciones financieras sobre BIND ERP</strong>, no un reemplazo. BIND conserva su rol como fuente de verdad para facturación, timbrado CFDI y contabilidad; la plataforma orquesta la visibilidad, las reglas de cobranza, el tablero directivo y la trazabilidad que hoy no existen de forma centralizada.</p>
<p class="mini-label">Alcance funcional del MVP</p>
<ul>
<li>Integración con BIND ERP vía API: lectura primero y <strong>escritura asistida</strong> una vez validada en Discovery.</li>
<li><strong>Emisión de facturas desde la plataforma:</strong> creación de cotización y conversión a factura en MXN (con IVA) y USD (sin IVA, para clientes extranjeros) a través de la API de BIND, con timbrado a cargo del PAC de BIND, confirmación humana obligatoria y modo de simulación (<em>dry-run</em>) previo a cada emisión.</li>
<li>Catálogo central de clientes con lista blanca configurable (ACUNTIA + Top 3).</li>
<li>Vista unificada de facturación: estados, vencimientos, filtros y descarga de PDF/XML.</li>
<li>Cobranza operativa: antigüedad de cartera (<em>aging</em>) y alertas internas para el equipo de Finanzas.</li>
<li>Tablero financiero con indicadores de cuentas por cobrar, vencidas y próximas a vencer.</li>
<li>Reportes exportables en CSV/XLSX; operación multimoneda MXN y USD con tipo de cambio del DOF capturado al momento de la facturación.</li>
<li>Autenticación y control de acceso por roles (Finanzas, Dirección, Operaciones, Administración); arquitectura preparada para multi-tenant y bitácora de auditoría universal.</li>
</ul>
<p class="mini-label">Visión integral (fases posteriores, cotizadas en el Anexo B)</p>
<div class="flow">
<span class="chip">Jira (horas)</span><span class="arrow"></span>
<span class="chip">BUK (nómina)</span><span class="arrow"></span>
<span class="chip accent">Plataforma</span><span class="arrow"></span>
<span class="chip">Factura</span><span class="arrow"></span>
<span class="chip">Cobranza</span><span class="arrow"></span>
<span class="chip">Conciliación</span><span class="arrow"></span>
<span class="chip">Asiento</span>
</div>
<p>La incorporación posterior de pagos en línea, recordatorios automáticos, conciliación bancaria, asientos contables o BUK se realiza <strong>sobre la misma base, sin reescribir lo construido</strong>.</p>
<div class="callout"><strong>Sobre la automatización y los "agentes".</strong> El MVP incluye automatización operativa <strong>por reglas</strong> (sincronización con BIND, alertas de vencimiento y reportes programados). Los <strong>agentes con IA/LLM</strong> (clasificación de transacciones, detección de anomalías) quedan para fase posterior, conforme el propio PRD los define como opcionales y a evaluar post-MVP.</div>
<h3><span class="sn">1.5</span>Criterios de éxito de la primera etapa</h3>
<ul>
<li>Emisión de facturas en MXN y USD desde la plataforma, con confirmación humana y sin incidentes fiscales.</li>
<li>Visibilidad en tiempo cercano a real del estado de facturación y cuentas por cobrar, para Dirección y Finanzas.</li>
<li>Reducción medible del trabajo manual de consulta y reporteo sobre BIND.</li>
<li>Aplicación correcta de la lista blanca: ACUNTIA y Top 3 nunca reciben recordatorios automáticos masivos.</li>
<li>Entrega del MVP funcional en el plazo comprometido, con alcance acotado y sin desviaciones.</li>
<li>Control fiscal: ninguna factura se emite sin confirmación humana explícita; el timbrado CFDI permanece a cargo del PAC de BIND.</li>
</ul>
<h3><span class="sn">1.6</span>Escala operativa de referencia</h3>
<ul>
<li>45 colaboradores en nómina y 5 freelancers; aproximadamente 50 facturas emitidas al mes.</li>
<li>3 instituciones bancarias (2 México + IBC Bank Texas) — fuera de esta etapa.</li>
<li>2 jurisdicciones fiscales (México y Texas, impuestos estándar en el MVP).</li>
<li>Interlocutores operativos: Pedro (contacto técnico) y gerencia administrativa, con supervisión ejecutiva de Noe (CTO), Araceli (CEO) y Erika (PM).</li>
</ul>
</section>
<!-- ===== 2. Alcance detallado ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">02</div><div><h2>Alcance detallado</h2></div></div>
<div class="callout cool"><strong>Principio rector.</strong> Un MVP entrega valor real en el menor tiempo posible con el alcance <strong>mínimo viable</strong>, no con todo lo deseable. Lo que no forma parte de esta etapa se documenta en el roadmap (Anexo B) con cotización indicativa.</div>
<h3><span class="sn">2.1</span>Stack tecnológico propuesto</h3>
<p>La selección privilegia robustez empresarial, mantenibilidad a largo plazo y afinidad con Azure. El binomio <strong>.NET + Azure</strong> es una combinación nativa de Microsoft, lo que reduce fricción de despliegue, seguridad y operación para una aplicación financiera.</p>
<table class="compact">
<thead><tr><th>Capa</th><th>Tecnología</th><th>Versión objetivo</th><th>Justificación</th></tr></thead>
<tbody>
<tr><td class="k">Frontend</td><td>Angular + Angular Material</td><td>Angular 21 · Material 21</td><td>SPA escalable, fuertemente tipada; componentes de datos listos para facturación y cobranza.</td></tr>
<tr><td class="k">Backend</td><td>C# / .NET (ASP.NET Core Web API)</td><td>.NET 10 (LTS) · C# 14</td><td>Tipado fuerte, madurez empresarial y desempeño; soporte de primera clase en Azure.</td></tr>
<tr><td class="k">Acceso a datos</td><td>Entity Framework Core (Npgsql)</td><td>EF Core 10 · Npgsql 10</td><td>Modelo tipado y migraciones versionadas, con control fino para multi-tenant y bitácora.</td></tr>
<tr><td class="k">Base de datos</td><td>Azure Database for PostgreSQL Flexible</td><td>PostgreSQL 17</td><td>Base productiva administrada (Azure SQL queda como alternativa nativa si Balam lo prefiere).</td></tr>
<tr><td class="k">Infraestructura</td><td>App Service · Key Vault · App Insights · Blob Storage</td><td>runtime .NET 10 (Linux)</td><td>Cómputo administrado, secretos cifrados y observabilidad integrados.</td></tr>
<tr><td class="k">Integración BIND</td><td>Cliente .NET tipado sobre la API OData</td><td>.NET 10 · HttpClient + Polly</td><td>Reintentos, manejo de límites de uso y mapeo de errores; contrato validado en el sandbox de descubrimiento.</td></tr>
<tr><td class="k">Control de acceso</td><td>ASP.NET Core Identity + JWT</td><td>incluido en .NET 10</td><td>Cuatro roles (Finanzas, Dirección, Operaciones, Administración).</td></tr>
<tr><td class="k">Entrega continua</td><td>GitHub + CI/CD (Actions / Azure DevOps)</td><td></td><td>Despliegues controlados con validación automatizada.</td></tr>
</tbody>
</table>
<p>El proyecto se organiza en cuatro etapas secuenciales. Cada una concluye con un entregable demostrable y se factura conforme a horas reales (ver §3).</p>
<div class="etapa"><div class="etapa-h">Etapa 0 — Discovery, infraestructura y prototipo visual<span class="tag">Semana 1 · 1822 h</span></div>
Validar el comportamiento real de la API de BIND con la cuenta de Balam, preparar la infraestructura en Azure (App Service, PostgreSQL Flexible, Storage, Key Vault, App Insights), dejar el repositorio con CI/CD y <strong>entregar un prototipo visual navegable</strong> de las 56 pantallas principales con el manual de marca aplicado. Incluye documento de hallazgos y ADRs iniciales.</div>
<div class="callout"><strong>Riesgo a validar en Discovery.</strong> BIND documenta públicamente <code>Customers</code>, <code>Products</code> y <code>Activities</code>; <code>Invoices</code> y <code>Payments</code> son esperados, pero los nombres exactos de campos y el saldo abierto por factura solo se confirman con la cuenta real. <strong>Plan A</strong> (BIND expone el saldo) y <strong>Plan B</strong> (saldo = total pagos) están cubiertos por el rango; el <strong>Plan C</strong> (interfaz propia de registro manual de pagos, +68 h <strong>no incluidas</strong>) se evaluaría y aprobaría al cierre de la etapa.</div>
<div class="etapa"><div class="etapa-h">Etapa 1 — Plataforma base y sincronización con BIND<span class="tag">Semanas 2-3 · 3239 h</span></div>
Backend con autenticación, roles y bitácora de auditoría universal; arquitectura multi-tenant a nivel de esquema (<code>tenant_id</code> + RLS); cliente de la API de BIND con doble encabezado, reintentos con backoff y constructor OData; <strong>capa de escritura controlada</strong> (cotización → factura) envuelta en <em>dry-run</em>, confirmación y feature flag; sincronización programada de clientes y facturas; modelo central de facturas con estados.</div>
<div class="callout cool"><strong>Nota fiscal.</strong> La plataforma <strong>no timbra directamente</strong>; el timbrado CFDI permanece en el PAC integrado de BIND. Toda operación de escritura permanece desactivada (feature flag) hasta validar su comportamiento en Discovery, y ninguna emisión ocurre sin confirmación humana explícita.</div>
<div class="etapa"><div class="etapa-h">Etapa 2 — Facturación (consulta y emisión), catálogo y multimoneda<span class="tag">Semanas 4-5 · 3846 h</span></div>
Implementación productiva de las pantallas validadas; listado de facturas con filtros, búsqueda y detalle con PDF/XML; <strong>creación de cotizaciones y conversión a factura</strong> vía API de BIND; <strong>emisión multimoneda</strong> MXN (con IVA) y USD (sin IVA); <strong>flujo con confirmación humana obligatoria y <em>dry-run</em></strong>; catálogo de clientes con lista blanca configurable (ACUNTIA + Top 3); tipo de cambio del DOF; exportación CSV/XLSX; manual de usuario.</div>
<div class="callout cool"><strong>Fuera de esta etapa:</strong> generación automática de facturas desde eventos (Jira → BUK → factura), soporte EUR e integración con BUK. El timbrado CFDI permanece a cargo del PAC de BIND.</div>
<div class="etapa"><div class="etapa-h">Etapa 3 — Cobranza, tablero, reportes y cierre<span class="tag">Semanas 6-7 · 2429 h</span></div>
Módulo de cobranza (antigüedad 30/60/90 días, vistas por vencer y vencidas); alertas internas configurables para Finanzas (5 días antes del vencimiento); aplicación de lista blanca (ACUNTIA + Top 3 sin alertas masivas); tablero directivo de CxC; reporte operativo configurable (CSV/XLSX/PDF); endurecimiento de seguridad, respaldos y plan de recuperación; documentación (README, runbook, ADRs); capacitación grabada (1.52 h) y soporte posterior al lanzamiento de 2 semanas.</div>
<div class="callout cool"><strong>Fuera de esta etapa:</strong> recordatorios automáticos a clientes, pagos en línea, conciliación bancaria, asientos contables automáticos e integración bancaria en vivo.</div>
</section>
<!-- ===== 3. Modelo de colaboración ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">03</div><div><h2>Modelo de colaboración</h2></div></div>
<p class="lede">El proyecto se desarrolla bajo un esquema de <strong>horas reales con tarifa transparente</strong>, no de precio cerrado.</p>
<h3><span class="sn">3.1</span>Esquema: tiempo y materiales con tope por etapa</h3>
<p>El alcance preciso de la integración con BIND —campos exactos, comportamiento de <code>Invoices</code> y <code>Payments</code>, manejo de saldos— solo se conoce al inspeccionar la API en vivo con la cuenta de Balam. Un precio fijo obligaría a incorporar un margen de contingencia que encarecería el proyecto de forma innecesaria. A cambio de esta flexibilidad, el esquema ofrece controles concretos:</p>
<ul>
<li><strong>Rango estimado por etapa</strong> (mínimomáximo de horas), acordado por escrito antes de iniciar cada una.</li>
<li><strong>Tope por etapa:</strong> de aproximarse al máximo del rango, el trabajo se detiene y se revisa el alcance con Balam antes de continuar.</li>
<li><strong>Reporte semanal</strong> de horas trabajadas con desglose por tarea, y <strong>demostración semanal</strong> del avance entregado.</li>
</ul>
<h3><span class="sn">3.2</span>Dedicación y plazo</h3>
<p>La dedicación es de <strong>media jornada (aproximadamente 20 h/semana</strong>, con flexibilidad hasta 25 h en semanas de mayor carga). Sobre esa base, la primera etapa requiere <strong>112 a 136 horas</strong>, equivalentes a <strong>6 7 semanas</strong> de calendario.</p>
<h3><span class="sn">3.3</span>Tarifa y facturación</h3>
<ul>
<li><strong>Tarifa:</strong> <strong>$600 MXN por hora trabajada</strong> + IVA, con comprobante fiscal CFDI 4.0. Aplica también a las fases posteriores (Anexo B) y a la bolsa de horas de soporte (§5).</li>
<li><strong>Facturación:</strong> por etapa, conforme se entrega cada una (con anexo de desglose de horas por tarea). La Etapa 0 se factura al inicio del proyecto.</li>
<li><strong>Pago:</strong> a <strong>30 días naturales</strong> posteriores a la factura, mediante transferencia, conforme a la política de Balam.</li>
<li><strong>Arranque sin anticipo:</strong> conforme a la política de Balam, no se maneja anticipo. La <strong>Etapa 0 (30 horas, $18,000 MXN + IVA)</strong> se factura al inicio y su pago corre a 30 días, igual que los avances. El arranque queda condicionado a la <strong>firma del contrato / orden de trabajo</strong>, que formaliza el compromiso de ambas partes en sustitución del anticipo.</li>
</ul>
<h3><span class="sn">3.4</span>Comunicación</h3>
<ul>
<li>Reportes asíncronos 23 veces por semana (avance, siguientes pasos y bloqueadores).</li>
<li>Demostración semanal (30 min, viernes) con Pedro y la gerencia administrativa.</li>
<li>Sesión técnica quincenal (1 h) con Noe para decisiones de arquitectura y validación de reglas de negocio.</li>
<li>Disponibilidad para reuniones urgentes con 24 h de anticipación, en horario laboral (9:0018:00, CST).</li>
</ul>
<h3><span class="sn">3.5</span>Metodología de desarrollo asistido por IA</h3>
<p>El desarrollo se apoya en herramientas de asistencia por IA (Claude Code, de Anthropic) como acelerador de productividad. Por transparencia y cumplimiento:</p>
<ul>
<li><strong>Responsabilidad humana.</strong> Todo el código entregado es revisado y validado por el proveedor; la responsabilidad de cada línea integrada al repositorio es de Johann Velazquez.</li>
<li><strong>Protección de datos.</strong> Ningún dato real de Balam se comparte con modelos externos; el desarrollo usa datos sintéticos derivados de la estructura, no del contenido. Las credenciales residen en gestores de secretos cifrados.</li>
<li><strong>Cumplimiento.</strong> Sin entrenamiento ni telemetría sobre datos del cliente, y con el código alojado en el repositorio de Balam desde el primer día.</li>
<li><strong>Auditabilidad.</strong> Las decisiones de arquitectura quedan registradas en ADRs versionados.</li>
</ul>
</section>
<!-- ===== 4. Inversión ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">04</div><div><h2>Inversión</h2></div></div>
<p>La inversión de la primera etapa se desglosa por entregable, conforme al esquema de tiempo y materiales:</p>
<table>
<thead><tr><th>Etapa</th><th>Entregable principal</th><th class="num">Rango (horas)</th><th class="num">Inversión (MXN)</th></tr></thead>
<tbody>
<tr><td class="k">0</td><td>Discovery, infraestructura Azure y prototipo visual navegable</td><td class="num">18 22</td><td class="num">$10,800 $13,200</td></tr>
<tr><td class="k">1</td><td>Plataforma base, sincronización con BIND y capa de escritura</td><td class="num">32 39</td><td class="num">$19,200 $23,400</td></tr>
<tr><td class="k">2</td><td>Facturación (consulta y emisión MXN/USD), catálogo y multimoneda</td><td class="num">38 46</td><td class="num">$22,800 $27,600</td></tr>
<tr><td class="k">3</td><td>Cobranza, tablero, reportes y cierre</td><td class="num">24 29</td><td class="num">$14,400 $17,400</td></tr>
<tr class="total"><td>Total primera etapa</td><td></td><td class="num">112 136 h</td><td class="num">$67,200 $81,600</td></tr>
</tbody>
</table>
<div class="callout cool">Cifras antes de IVA. Plazo estimado: <strong>6 7 semanas</strong> a media jornada (~20 h/semana, con flexibilidad hasta 25 h en semanas pico). El esquema de control por etapa busca que la inversión se concentre en la parte baja del rango; el margen superior cubre eventualidades del Discovery con BIND. Toda variación se comunica antes de incurrir en ella.</div>
</section>
<!-- ===== 5. Soporte ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">05</div><div><h2>Soporte posterior al MVP</h2></div></div>
<h3><span class="sn">5.1</span>Garantía incluida</h3>
<ul>
<li><strong>Defectos en funcionalidad entregada:</strong> cobertura sin costo durante <strong>45 días</strong> posteriores a la entrega de cada etapa. El plazo de 45 días cubre el primer cierre mensual de operación, cuando suelen hacerse visibles los defectos en un sistema financiero.</li>
<li><strong>Soporte posterior al lanzamiento:</strong> <strong>2 semanas</strong> tras el cierre del MVP, para corrección de defectos (no nuevo alcance).</li>
</ul>
<h3><span class="sn">5.2</span>Bolsa de horas de soporte (opcional)</h3>
<p>Concluida la garantía y el soporte incluidos, se ofrece una <strong>bolsa de horas</strong> para la operación y evolución incremental de la plataforma, contratable por bloques:</p>
<table>
<thead><tr><th>Bloque</th><th class="num">Horas</th><th class="num">Inversión (sin IVA)</th></tr></thead>
<tbody>
<tr><td class="k">Pequeño</td><td class="num">10 h</td><td class="num">$6,000</td></tr>
<tr class="sub-total"><td>Mediano <em>(recomendado)</em></td><td class="num">20 h</td><td class="num">$12,000</td></tr>
<tr><td class="k">Grande</td><td class="num">40 h</td><td class="num">$24,000</td></tr>
</tbody>
</table>
<p><strong>Vigencia:</strong> las horas son <strong>válidas por 12 meses</strong> desde su contratación, <strong>sin caducidad mensual</strong> (se consumen al ritmo que Balam necesite). Tarifa $600 MXN/h + IVA.</p>
<p><strong>Cubre:</strong> corrección de defectos fuera de garantía; ajustes menores y requerimientos pequeños; mantenimiento de dependencias y compatibilidad ante cambios de la API de BIND; atención a incidencias en 1 día hábil; y reporte de horas consumidas. <strong>No cubre:</strong> desarrollo de los módulos diferidos (Anexo B); migraciones o cambios mayores de arquitectura; ni incidentes atribuibles a terceros (Azure, BIND, SAT).</p>
</section>
<!-- ===== 6. Terceros ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">06</div><div><h2>Costos de servicios de terceros</h2></div></div>
<p>Los siguientes son servicios de infraestructura externos, independientes de los honorarios del proveedor (a cargo de Balam):</p>
<table class="compact">
<thead><tr><th>Servicio</th><th>Configuración</th><th class="num">Costo mensual (USD)</th></tr></thead>
<tbody>
<tr><td class="k">Azure App Service (Linux, Basic)</td><td>B1 (mínimo) → B2/B3 (recomendado); WebJob en el mismo plan</td><td class="num">$13 $51</td></tr>
<tr><td class="k">Azure Database for PostgreSQL Flexible</td><td>Burstable B1ms → B2s + 3264 GiB de almacenamiento y respaldo</td><td class="num">$16 $65</td></tr>
<tr><td class="k">Azure Blob Storage (Hot, LRS)</td><td>PDF/XML y exportaciones (pocos GB)</td><td class="num">$1 $5</td></tr>
<tr><td class="k">Key Vault + Application Insights</td><td>Secretos (Standard) + observabilidad (5 GB/mes incluidos)</td><td class="num">$0 $15</td></tr>
<tr><td class="k">Monitoreo / Sentry <em>(opcional)</em></td><td>Plan gratuito → Team</td><td class="num">$0 $26</td></tr>
<tr><td class="k">PAC para CFDI</td><td>Ya incluido en BIND</td><td class="num">Sin costo</td></tr>
<tr class="total"><td>Total mensual estimado</td><td></td><td class="num">$30 $160 USD/mes</td></tr>
</tbody>
</table>
<div class="callout cool"><strong>Configuración esperada en producción</strong> (App Service B2 + PostgreSQL B2s con respaldo + observabilidad básica): <strong>≈ $90 $110 USD/mes</strong>. El extremo bajo corresponde a una configuración mínima; el alto, a una holgada con redundancia y monitoreo de pago. Los costos pueden optimizarse con <strong>instancias reservadas</strong> (13 años, hasta ~3040% de ahorro), una vez validado el consumo real.</div>
<p>En fases posteriores se incorporarían: Stripe (3.6% + $3 MXN por transacción con tarjeta; ~$3 MXN por SPEI), Claude API para el procesamiento de estados de cuenta en PDF ($5 $20 USD/mes), correo transaccional ($0 $20 USD/mes) y, opcionalmente, un agregador bancario como Belvo ($200 $500 USD/mes).</p>
</section>
<!-- ===== 7. Supuestos ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">07</div><div><h2>Supuestos y dependencias</h2></div></div>
<p>El cumplimiento de las estimaciones depende de las siguientes condiciones. De no satisfacerse alguna, la etapa correspondiente se replantea de común acuerdo:</p>
<ol>
<li><strong>Acceso productivo a la API de BIND</strong>, con permisos de <strong>lectura y escritura</strong> (creación de cotización y conversión a factura) y documentación oficial disponible, al inicio de la Etapa 0.</li>
<li><strong>Cuenta de Azure de Balam</strong> (o autorización para crearla a su nombre) disponible al inicio de la Etapa 0.</li>
<li><strong>Manual de marca</strong> entregado al inicio de la Etapa 0.</li>
<li><strong>Reglas de negocio finales</strong> validadas en Discovery: lista blanca (ACUNTIA + Top 3), ciclos de cobranza, días de anticipación de alertas y frecuencia de sincronización.</li>
<li><strong>Disponibilidad de dos interlocutores</strong> (Pedro y la gerencia administrativa) con capacidad de resolver bloqueadores en menos de 48 horas.</li>
<li><strong>Producción como único ambiente disponible</strong> (sin sandbox de BIND). La plataforma opera en modo de simulación cuando exista riesgo de afectar BIND, requiriendo confirmación explícita antes de cualquier escritura.</li>
<li><strong>Acuerdo de uso de la metodología asistida por IA</strong>, con el compromiso de no entrenamiento sobre datos del cliente.</li>
<li><strong>El PAC integrado de BIND</strong> atiende los volúmenes sin costo adicional; el timbrado CFDI permanece a cargo de BIND, no de la plataforma.</li>
<li><strong>El comportamiento de escritura de la API de BIND</strong> se valida en Discovery; la emisión productiva se habilita únicamente tras esa validación. Balam designa al rol responsable de confirmar cada emisión.</li>
</ol>
</section>
<!-- ===== 8. Entregables ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">08</div><div><h2>Entregables y propiedad</h2></div></div>
<p>Más allá del código funcional, la entrega incluye:</p>
<ul>
<li><strong>Código fuente en el repositorio de Balam</strong> (GitHub) desde el primer día; la propiedad intelectual es de Balam.</li>
<li><strong>Documentación técnica viva</strong> en el repositorio.</li>
<li><strong>Pruebas automatizadas</strong> para los flujos críticos (sincronización con BIND, lista blanca, multimoneda y control de acceso).</li>
<li><strong>Pipeline de CI/CD</strong> operativo, con despliegues controlados.</li>
<li><strong>Respaldos automatizados</strong> y plan de recuperación documentado.</li>
<li><strong>Sesión de transferencia de conocimiento</strong> grabada, y <strong>soporte posterior al lanzamiento</strong> de 2 semanas (corrección de defectos).</li>
</ul>
</section>
<!-- ===== 9. Fuera de alcance ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">09</div><div><h2>Fuera del alcance de esta etapa</h2></div></div>
<ul>
<li>Rediseño visual integral o sistema de diseño propio (se emplea la identidad del manual de marca de Balam; un diseño a la medida requeriría sumar un diseñador).</li>
<li>Adquisición de licencias de terceros (PAC, agregadores, hosting Azure).</li>
<li>Gestión del cambio organizacional más allá de la sesión de capacitación.</li>
<li>Módulos diferidos: pagos en línea, recordatorios automáticos a clientes, conciliación bancaria, asientos contables, BUK e IA — cotizados de forma indicativa en el Anexo B.</li>
<li>Integraciones no listadas (Book, CRM, etc.), cotizables por separado.</li>
<li>Adaptaciones derivadas de cambios fiscales del SAT o modificaciones disruptivas en la API de BIND que impliquen retrabajo mayor (atendibles mediante la bolsa de horas de soporte, §5.2).</li>
</ul>
</section>
<!-- ===== 10. Garantía y condiciones ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">10</div><div><h2>Garantía y condiciones generales</h2></div></div>
<ul>
<li><strong>Garantía de defectos:</strong> cobertura sin costo durante 45 días posteriores a la entrega de cada etapa.</li>
<li><strong>Control de cambios:</strong> toda funcionalidad fuera del alcance acordado se documenta como solicitud de cambio (Change Request), se estima y se aprueba por escrito antes de ejecutarse.</li>
<li><strong>Propiedad intelectual:</strong> el código es propiedad de Balam desde el primer commit. El proveedor conserva el derecho de referir el proyecto en su portafolio sin divulgar información confidencial.</li>
<li><strong>Responsabilidad del código asistido por IA:</strong> todo defecto queda cubierto por la misma garantía. La responsabilidad final del código corresponde al proveedor, con independencia de las herramientas utilizadas.</li>
</ul>
<div class="callout cool">Las condiciones contractuales detalladas (confidencialidad, límite de responsabilidad, jurisdicción y demás cláusulas) se formalizan en el contrato de prestación de servicios previo al inicio.</div>
</section>
<!-- ===== 11. Próximos pasos ===== -->
<section class="sec">
<div class="sec-head"><div class="sec-num">11</div><div><h2>Próximos pasos</h2></div></div>
<ol>
<li>Sesión de revisión de esta propuesta (1 h) para resolver dudas y ajustar lo que corresponda.</li>
<li><strong>Firma del contrato / orden de trabajo</strong> (prestación de servicios y confidencialidad) — formaliza el compromiso para arrancar sin anticipo.</li>
<li><strong>Factura de la Etapa 0</strong> (30 horas, <strong>$18,000 MXN</strong> + IVA), con pago a 30 días, para iniciar Discovery.</li>
<li>Arranque del proyecto, con entrega del MVP en <strong>6 7 semanas</strong>.</li>
</ol>
<div class="signoff">
<div class="nm">Johann Velazquez</div>
<div class="rl">Consultor de Software · Monterrey, Nuevo León</div>
</div>
</section>
<!-- ===== Anexo A ===== -->
<section class="sec break">
<div class="sec-head"><div class="sec-num">A</div><div><span class="kick">Anexo</span><h2>Trazabilidad PRD ↔ MVP</h2></div></div>
<p class="lede">Este anexo relaciona cada funcionalidad y requerimiento del <strong>PRD de Balam</strong> con el alcance comprometido en la primera etapa.</p>
<div class="callout cool"><strong>Leyenda:</strong>&nbsp;&nbsp;✅ Cubierto&nbsp;&nbsp;·&nbsp;&nbsp;⚠️ Parcial&nbsp;&nbsp;·&nbsp;&nbsp;❌ Diferido a fase posterior (Anexo B)</div>
<h3>A.1 Funcionalidades (PRD §3.1)</h3>
<h4>Facturación</h4>
<table class="compact">
<thead><tr><th>Funcionalidad PRD</th><th class="ctr" style="width:62pt;">Cobertura</th><th>Detalle</th></tr></thead>
<tbody>
<tr><td class="k">Generación automatizada de facturas</td><td class="cov">⚠️</td><td>Creación asistida (cotización → factura) vía API de BIND con confirmación humana; el timbrado lo realiza el PAC de BIND. La generación automática desde eventos (Jira→BUK→Factura) — fase posterior.</td></tr>
<tr><td class="k">Multimoneda para clientes internacionales (USD sin IVA)</td><td class="cov"></td><td>Emisión en MXN (con IVA) y USD (sin IVA) con tipo de cambio del DOF. <strong>EUR queda fuera del MVP.</strong></td></tr>
<tr><td class="k">Descarga automática de facturas / reporte</td><td class="cov"></td><td>Listado con filtros, búsqueda, detalle con PDF/XML y exportación a Excel.</td></tr>
</tbody>
</table>
<h4>Cobranza</h4>
<table class="compact">
<thead><tr><th>Funcionalidad PRD</th><th class="ctr" style="width:62pt;">Cobertura</th><th>Detalle</th></tr></thead>
<tbody>
<tr><td class="k">Registro automático de pagos</td><td class="cov"></td><td>Diferido (vía webhook de pagos en línea). Los pagos se reflejan según <code>Payments</code> de BIND.</td></tr>
<tr><td class="k">Identificación de pagos por cliente</td><td class="cov">⚠️</td><td>Vista de saldos por cliente con base en datos de BIND; conciliación avanzada en fase posterior.</td></tr>
<tr><td class="k">Seguimiento de CxC con lista blanca ACUNTIA y Top 3</td><td class="cov"></td><td>Lista blanca configurable, aging y alertas internas. <strong>Recordatorios a clientes → fase posterior.</strong></td></tr>
</tbody>
</table>
<h4>Conciliación bancaria</h4>
<table class="compact">
<thead><tr><th>Funcionalidad PRD</th><th class="ctr" style="width:62pt;">Cobertura</th><th>Detalle</th></tr></thead>
<tbody>
<tr><td class="k">Conciliación automática pagos vs. facturas</td><td class="cov"></td><td>Diferido — coincidencia exacta, por alias y cola de revisión humana.</td></tr>
<tr><td class="k">Integración con estados de cuenta (3 bancos, incl. IBC Texas)</td><td class="cov"></td><td>Diferido — carga de PDF y extracción estructurada.</td></tr>
<tr><td class="k">Identificación de discrepancias</td><td class="cov"></td><td>Diferido.</td></tr>
</tbody>
</table>
<h4>Contabilidad · Reportes y alertas</h4>
<table class="compact">
<thead><tr><th>Funcionalidad PRD</th><th class="ctr" style="width:62pt;">Cobertura</th><th>Detalle</th></tr></thead>
<tbody>
<tr><td class="k">Generación automática de asientos contables</td><td class="cov"></td><td>Diferido. Depende de la capacidad de escritura validada de la API de BIND.</td></tr>
<tr><td class="k">Integración con sistema contable existente</td><td class="cov"></td><td>Diferido — exportación en formato BIND para carga manual.</td></tr>
<tr><td class="k">Tablero de estado financiero</td><td class="cov"></td><td>Indicadores de CxC totales, por cliente, vencidas y próximas a vencer.</td></tr>
<tr><td class="k">Alerta — pagos pendientes</td><td class="cov"></td><td>Alertas internas para Finanzas.</td></tr>
<tr><td class="k">Alerta — errores en conciliación</td><td class="cov"></td><td>Diferido (sin conciliación en el MVP).</td></tr>
<tr><td class="k">Alerta — facturas no cobradas</td><td class="cov"></td><td>Alertas internas; envío automático a clientes → fase posterior.</td></tr>
</tbody>
</table>
<h3>A.2 Requerimientos funcionales (PRD §6)</h3>
<table class="compact">
<thead><tr><th style="width:46pt;">ID</th><th>Descripción</th><th class="ctr" style="width:56pt;">Cobertura</th><th>Notas</th></tr></thead>
<tbody>
<tr><td class="k">RF-01</td><td>Generar facturas automáticamente desde eventos</td><td class="cov">⚠️</td><td>Creación manual (cotización→factura) incluida; automática desde eventos requiere Jira/BUK — fase posterior</td></tr>
<tr><td class="k">RF-02</td><td>Multimoneda USD/EUR/MXN</td><td class="cov">⚠️</td><td>MXN + USD en el MVP; EUR en fase posterior</td></tr>
<tr><td class="k">RF-03</td><td>Integración con sistemas existentes (BUK / nómina)</td><td class="cov"></td><td>API de BUK confirmada, no prioritaria</td></tr>
<tr><td class="k">RF-04</td><td>Registrar pagos desde fuentes bancarias</td><td class="cov"></td><td>Fase posterior</td></tr>
<tr><td class="k">RF-05</td><td>Asociar pagos a facturas</td><td class="cov">⚠️</td><td>Con base en <code>Payments</code> de BIND; conciliación avanzada después</td></tr>
<tr><td class="k">RF-06</td><td>Identificar pagos parciales y completos</td><td class="cov">⚠️</td><td>Según lo que reporte BIND</td></tr>
<tr><td class="k">RF-07</td><td>Conciliar transacciones bancarias</td><td class="cov"></td><td>Fase posterior</td></tr>
<tr><td class="k">RF-08</td><td>Detectar discrepancias</td><td class="cov"></td><td>Fase posterior</td></tr>
<tr><td class="k">RF-09</td><td>Reportes de conciliación</td><td class="cov"></td><td>Fase posterior</td></tr>
<tr><td class="k">RF-10</td><td>Asientos contables automáticos</td><td class="cov"></td><td>Fase posterior</td></tr>
<tr><td class="k">RF-11</td><td>Integración con sistema contable</td><td class="cov"></td><td>Fase posterior (exportación en formato BIND)</td></tr>
<tr><td class="k">RF-12</td><td>Tablero financiero en tiempo real</td><td class="cov"></td><td>Incluido</td></tr>
<tr><td class="k">RF-13</td><td>Alertas configurables</td><td class="cov">⚠️</td><td>Internas en el MVP; a clientes en fase posterior</td></tr>
<tr><td class="k">RF-14</td><td>Reportes exportables</td><td class="cov"></td><td>Incluido</td></tr>
</tbody>
</table>
<h3>A.3 Requerimientos no funcionales (PRD §7)</h3>
<table class="compact">
<thead><tr><th style="width:46pt;">ID</th><th>Descripción</th><th class="ctr" style="width:56pt;">Cobertura</th><th>Notas</th></tr></thead>
<tbody>
<tr><td class="k">RNF-01</td><td>Arquitectura modular y escalable</td><td class="cov"></td><td>Multi-tenant a nivel de esquema (<code>tenant_id</code> + RLS)</td></tr>
<tr><td class="k">RNF-02</td><td>Alta disponibilidad</td><td class="cov">⚠️</td><td>MVP en una sola región de Azure; HA multi-región → fase posterior</td></tr>
<tr><td class="k">RNF-03</td><td>Seguridad de datos financieros</td><td class="cov"></td><td>Secretos en Key Vault, control de acceso, bitácora y endurecimiento</td></tr>
<tr><td class="k">RNF-04</td><td>Cumplimiento fiscal (México y Texas)</td><td class="cov">⚠️</td><td>Impuestos estándar en el MVP; cumplimiento avanzado Texas → fase posterior</td></tr>
<tr><td class="k">RNF-05</td><td>Integración con APIs externas</td><td class="cov"></td><td>BIND API y DOF para tipo de cambio</td></tr>
<tr><td class="k">RNF-06</td><td>Trazabilidad completa de operaciones</td><td class="cov"></td><td>Bitácora de auditoría universal</td></tr>
</tbody>
</table>
<h3>A.4 Diferidos — resumen</h3>
<p><strong>Bloqueado por terceros o sin prioridad actual:</strong> generación automática de facturas (Jira→BUK→Factura); integración bancaria en vivo (Belvo/Plaid/IBC Texas); integración con BUK; integración con Jira.</p>
<p><strong>Diferido por decisión de alcance:</strong> pagos en línea; recordatorios automáticos a clientes; conciliación bancaria; asientos contables automáticos; EUR; flujo de efectivo proyectado; detección avanzada de anomalías; portal de cliente; multi-tenant comercial; cumplimiento avanzado Texas.</p>
</section>
<!-- ===== Anexo B ===== -->
<section class="sec break">
<div class="sec-head"><div class="sec-num">B</div><div><span class="kick">Anexo</span><h2>Cotización indicativa de fases posteriores</h2></div></div>
<p class="lede">Los siguientes son <strong>rangos indicativos, no compromisos contractuales</strong>. Se reconfirman mediante su propio Discovery al activar cada módulo, con datos reales de uso del MVP. La tarifa de $600 MXN/h y el esquema de tiempo y materiales con tope por etapa se mantienen.</p>
<h3>B.1 Módulos cotizados</h3>
<table>
<thead><tr><th>Módulo</th><th class="num">Horas</th><th class="num">Inversión (MXN)</th></tr></thead>
<tbody>
<tr><td class="k">Recordatorios automáticos por correo + editor de plantillas + bitácora de comunicaciones</td><td class="num">12 16</td><td class="num">$7,200 $9,600</td></tr>
<tr><td class="k">Pago en línea (Stripe Checkout) + webhook de conciliación + tarjeta y SPEI</td><td class="num">14 18</td><td class="num">$8,400 $10,800</td></tr>
<tr><td class="k">Conciliación bancaria por PDF (3 bancos, incl. IBC Texas) + extracción asistida + motor de coincidencia + cola de revisión</td><td class="num">28 36</td><td class="num">$16,800 $21,600</td></tr>
<tr><td class="k">Exportación de pagos conciliados y movimientos en formato BIND</td><td class="num">8 12</td><td class="num">$4,800 $7,200</td></tr>
<tr><td class="k">Integración con BUK (lectura de nómina y colaboradores)</td><td class="num">18 24</td><td class="num">$10,800 $14,400</td></tr>
<tr class="total"><td>Subtotal</td><td class="num">80 106 h</td><td class="num">$48,000 $63,600</td></tr>
</tbody>
</table>
<div class="callout">El monto de esta fase es comparable al del MVP porque incorpora <strong>cinco módulos nuevos completos</strong>, cada uno con su propio diseño, integración y pruebas. No se trata de extensiones menores, sino de capacidades adicionales sobre la base ya construida.</div>
<h3>B.2 Roadmap posterior (sin cotización)</h3>
<p>Se cotiza al cierre de la fase anterior, priorizando con datos reales de uso: integración bancaria en vivo (Belvo, Plaid); conector con BIND para asientos contables automáticos; integración con Jira; flujo de efectivo proyectado (30/60/90 días); gestión cambiaria automática; pagos recurrentes; detección avanzada de anomalías; soporte EUR; clasificación asistida por IA; portal de cliente; multi-tenant comercial; aplicación móvil para tickets; y cumplimiento avanzado para Texas.</p>
<h3>B.3 Activación</h3>
<p>Al cierre del MVP se realiza una sesión de priorización (1 h) y se formaliza una orden de trabajo por módulo seleccionado. La tarifa, el esquema (facturación por etapa con pago a 30 días, sin anticipo), la comunicación y la garantía aplican en los mismos términos que en la primera etapa.</p>
</section>
</main>
</body>
</html>
Binary file not shown.
+686
View File
@@ -0,0 +1,686 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Balam · Arquitectura Fase 1 vs. PRD original</title>
<style>
:root{
--bg:#ffffff;
--ink:#0f172a;
--ink-2:#1e293b;
--muted:#64748b;
--muted-2:#94a3b8;
--line:#e2e8f0;
--line-2:#cbd5e1;
--soft:#f8fafc;
--soft-2:#f1f5f9;
/* status colors */
--in:#16a34a;
--in-bg:#dcfce7;
--in-ink:#14532d;
--in-line:#86efac;
--partial:#d97706;
--partial-bg:#fef3c7;
--partial-ink:#92400e;
--partial-line:#fcd34d;
--f2:#1d4ed8;
--f2-bg:#dbeafe;
--f2-ink:#1e3a8a;
--f2-line:#93c5fd;
--f3:#64748b;
--f3-bg:#f1f5f9;
--f3-ink:#475569;
--f3-line:#cbd5e1;
}
*{box-sizing:border-box}
html,body{background:var(--bg)}
body{
margin:0 auto; padding:48px 56px; max-width:1320px;
font-family:-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif;
color:var(--ink); line-height:1.5;
}
h1{font-size:36px; font-weight:800; margin:0 0 8px; letter-spacing:-0.025em}
.lede{color:var(--muted); font-size:16px; margin:0 0 36px; max-width:860px; line-height:1.55}
.lede strong{color:var(--ink)}
.section-label{
font-size:11px; font-weight:600; color:var(--muted);
letter-spacing:2px; text-transform:uppercase;
margin:0 0 12px;
}
.block-title{
font-size:22px; font-weight:700; margin:0 0 6px;
display:flex; align-items:center; gap:12px; letter-spacing:-0.015em;
}
.block-sub{color:var(--muted); font-size:14px; margin:0 0 24px; max-width:780px; line-height:1.55}
/* ============ summary numbers ============ */
.summary{
display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin:0 0 56px;
}
.stat{
border:1px solid var(--line); border-radius:12px; padding:18px 20px; background:#fff;
}
.stat-num{font-size:32px; font-weight:800; letter-spacing:-0.03em; line-height:1.1; margin-bottom:4px}
.stat-label{font-size:12px; color:var(--muted); font-weight:600; text-transform:uppercase; letter-spacing:0.06em; margin-bottom:8px}
.stat-sub{font-size:13px; color:var(--ink-2)}
.stat.in{border-color:var(--in-line); background:#f0fdf4}
.stat.in .stat-num{color:var(--in)}
.stat.partial{border-color:var(--partial-line); background:#fffbeb}
.stat.partial .stat-num{color:var(--partial)}
.stat.f2{border-color:var(--f2-line); background:#eff6ff}
.stat.f2 .stat-num{color:var(--f2)}
.stat.f3{border-color:var(--f3-line); background:var(--f3-bg)}
.stat.f3 .stat-num{color:var(--f3-ink)}
/* ============ legend ============ */
.legend{
display:flex; gap:14px; flex-wrap:wrap; margin:0 0 28px;
padding:14px 18px; border:1px solid var(--line); background:var(--soft); border-radius:10px;
}
.legend-item{display:flex; align-items:center; gap:8px; font-size:12.5px; color:var(--ink-2)}
.lg-chip{width:14px; height:14px; border-radius:4px; border:1px solid}
.lg-chip.in{background:var(--in-bg); border-color:var(--in-line)}
.lg-chip.partial{background:var(--partial-bg); border-color:var(--partial-line)}
.lg-chip.f2{background:var(--f2-bg); border-color:var(--f2-line)}
.lg-chip.f3{background:var(--f3-bg); border-color:var(--f3-line)}
/* ============ architecture stack ============ */
.arch{margin:0 0 64px}
.arch-stack{display:flex; flex-direction:column; gap:14px}
.layer{
display:grid; grid-template-columns:170px 1fr; gap:18px; align-items:stretch;
}
.layer-label{
background:var(--ink); color:#fff; border-radius:10px; padding:18px 16px;
display:flex; flex-direction:column; justify-content:center; gap:4px;
}
.layer-num{
width:26px; height:26px; border-radius:50%; background:#fff; color:var(--ink);
display:grid; place-items:center; font-weight:700; font-size:13px; margin-bottom:6px;
}
.layer-name{font-weight:700; font-size:14px; letter-spacing:-0.01em}
.layer-sub{font-size:11.5px; color:#94a3b8}
.layer-content{
border:1px solid var(--line); border-radius:10px; padding:16px;
display:flex; flex-wrap:wrap; gap:10px; background:#fff;
}
.mod{
border:1.5px solid; border-radius:8px; padding:10px 14px;
min-width:140px; flex:0 1 auto; position:relative;
}
.mod.in{background:var(--in-bg); border-color:var(--in-line); color:var(--in-ink)}
.mod.partial{background:var(--partial-bg); border-color:var(--partial-line); color:var(--partial-ink)}
.mod.f2{background:var(--f2-bg); border-color:var(--f2-line); color:var(--f2-ink)}
.mod.f3{background:var(--f3-bg); border-color:var(--f3-line); color:var(--f3-ink)}
.mod-title{font-weight:700; font-size:13.5px; letter-spacing:-0.005em}
.mod-sub{font-size:11.5px; opacity:0.85; margin-top:2px}
.mod-tag{
position:absolute; top:-7px; right:8px; background:#fff;
font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:0.08em;
padding:1px 5px; border-radius:3px; border:1px solid currentColor;
}
/* ============ side-by-side comparison ============ */
.compare{
display:grid; grid-template-columns:1fr 1fr; gap:18px; margin:0 0 56px;
}
.col{
border:1px solid var(--line); border-radius:12px; padding:24px; background:#fff;
}
.col.prd{border-top:4px solid var(--muted)}
.col.f1{border-top:4px solid var(--in)}
.col-head{margin-bottom:16px}
.col-tag{
display:inline-block; font-size:11px; font-weight:700; letter-spacing:0.1em;
text-transform:uppercase; padding:3px 8px; border-radius:4px; margin-bottom:8px;
}
.col-tag.prd{background:var(--soft-2); color:var(--muted)}
.col-tag.f1{background:var(--in-bg); color:var(--in-ink)}
.col-title{font-size:18px; font-weight:700; letter-spacing:-0.015em}
.col-sub{font-size:13px; color:var(--muted); margin-top:4px}
.col ul{margin:14px 0 0; padding:0; list-style:none}
.col li{
padding:10px 0; border-bottom:1px solid var(--soft-2); font-size:13.5px;
display:flex; gap:10px; align-items:flex-start;
}
.col li:last-child{border-bottom:none}
.col li .ico{
width:18px; height:18px; border-radius:50%; flex-shrink:0; display:grid; place-items:center;
font-size:11px; font-weight:700; margin-top:1px;
}
.ico.check{background:var(--in-bg); color:var(--in)}
.ico.partial{background:var(--partial-bg); color:var(--partial)}
.ico.out{background:var(--f2-bg); color:var(--f2)}
.ico.future{background:var(--f3-bg); color:var(--f3-ink)}
.col li strong{font-weight:600; color:var(--ink)}
.col li .note{display:block; color:var(--muted); font-size:12px; margin-top:2px}
/* ============ coverage matrix ============ */
.matrix{
border:1px solid var(--line); border-radius:12px; overflow:hidden; background:#fff;
margin:0 0 56px;
}
table{width:100%; border-collapse:collapse}
thead th{
background:var(--soft); border-bottom:1px solid var(--line); padding:12px 14px;
text-align:left; font-size:11px; color:var(--muted); font-weight:700;
text-transform:uppercase; letter-spacing:0.08em;
}
tbody td{
padding:12px 14px; border-bottom:1px solid var(--soft-2); font-size:13px; vertical-align:top;
}
tbody tr:last-child td{border-bottom:none}
.rf-id{font-family:ui-monospace,"SF Mono","Cascadia Mono",monospace; font-size:11.5px; color:var(--muted-2); font-weight:600}
.cov-badge{
display:inline-flex; align-items:center; gap:5px; padding:3px 10px; border-radius:5px;
font-size:11px; font-weight:700; letter-spacing:0.02em;
}
.cov-badge.in{background:var(--in-bg); color:var(--in-ink); border:1px solid var(--in-line)}
.cov-badge.partial{background:var(--partial-bg); color:var(--partial-ink); border:1px solid var(--partial-line)}
.cov-badge.f2{background:var(--f2-bg); color:var(--f2-ink); border:1px solid var(--f2-line)}
.cov-badge.f3{background:var(--f3-bg); color:var(--f3-ink); border:1px solid var(--f3-line)}
/* ============ pillars ============ */
.pillars{display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin:0 0 56px}
.pillar{
border:1px solid var(--line); border-radius:12px; padding:22px; background:#fff;
border-top:4px solid;
}
.pillar.in{border-top-color:var(--in)}
.pillar.f2{border-top-color:var(--f2)}
.pillar.f3{border-top-color:var(--f3-ink)}
.pillar-head{display:flex; align-items:center; gap:10px; margin-bottom:8px}
.pillar-emoji{font-size:22px}
.pillar-title{font-size:16px; font-weight:700; letter-spacing:-0.01em}
.pillar-sub{font-size:12px; color:var(--muted); margin-bottom:14px}
.pillar ul{margin:0; padding:0; list-style:none; font-size:13px}
.pillar li{padding:6px 0; border-bottom:1px dashed var(--soft-2); color:var(--ink-2)}
.pillar li:last-child{border-bottom:none}
/* ============ note / footer ============ */
.note-box{
background:#fffbeb; border:1px solid #fde68a; color:#78350f;
padding:14px 18px; border-radius:10px; font-size:13px; margin:0 0 32px;
display:flex; gap:12px; align-items:flex-start;
}
.note-icon{flex-shrink:0; font-size:16px}
.footer{
margin-top:48px; padding-top:24px; border-top:1px solid var(--line);
font-size:12px; color:var(--muted); display:flex; justify-content:space-between;
}
</style>
</head>
<body>
<p class="section-label">Balam · MVP Fase 1</p>
<h1>Arquitectura Fase 1 vs. PRD original</h1>
<p class="lede">
El PRD original de Balam pidió una plataforma <strong>end-to-end</strong> que cubriera facturación, cobranza, conciliación bancaria, asientos contables y reportes. Después de la llamada con Noe del 19-may y su recorte explícito del 25-may
(<em>"primer interés es facturación, solo BIND ERP primero"</em>), la <strong>Fase 1 se acota a facturación-first sobre BIND — incluida la emisión asistida de facturas en MXN y USD</strong>.
Lo demás vive en <strong>Fase 2 (cotizada en Anexo B)</strong> y <strong>Fase 3+ (roadmap)</strong> sin perder la arquitectura objetivo.
</p>
<!-- ============ SUMMARY NUMBERS ============ -->
<div class="summary">
<div class="stat in">
<div class="stat-label">Cubierto en Fase 1</div>
<div class="stat-num">8</div>
<div class="stat-sub">capacidades del PRD (incl. emisión)</div>
</div>
<div class="stat partial">
<div class="stat-label">Parcial en Fase 1</div>
<div class="stat-num">6</div>
<div class="stat-sub">con cobertura acotada</div>
</div>
<div class="stat f2">
<div class="stat-label">Diferido a Fase 2</div>
<div class="stat-num">8</div>
<div class="stat-sub">cotizado en Anexo B</div>
</div>
<div class="stat f3">
<div class="stat-label">Roadmap Fase 3+</div>
<div class="stat-num">8</div>
<div class="stat-sub">sin cotización aún</div>
</div>
</div>
<!-- ============ LEGEND ============ -->
<div class="legend">
<div class="legend-item"><span class="lg-chip in"></span> Incluido en Fase 1 (MVP BIND-first)</div>
<div class="legend-item"><span class="lg-chip partial"></span> Parcial en Fase 1 — versión limitada</div>
<div class="legend-item"><span class="lg-chip f2"></span> Diferido a Fase 2 (cotizada en Anexo B)</div>
<div class="legend-item"><span class="lg-chip f3"></span> Roadmap Fase 3+ (sin cotización aún)</div>
</div>
<!-- ============ ARCHITECTURE STACK ============ -->
<section class="arch">
<p class="section-label">1 / Arquitectura objetivo</p>
<h2 class="block-title">Stack por capas · módulos por fase</h2>
<p class="block-sub">
Misma arquitectura objetivo del PRD. Lo que cambia es <strong>qué módulos se entregan en Fase 1</strong> y cuáles se conectan después sin rearquitectar.
</p>
<div class="arch-stack">
<!-- Layer 1: Usuarios -->
<div class="layer">
<div class="layer-label">
<div class="layer-num">1</div>
<div class="layer-name">Usuarios</div>
<div class="layer-sub">Roles y canales</div>
</div>
<div class="layer-content">
<div class="mod in">
<div class="mod-title">Finanzas</div>
<div class="mod-sub">Operación diaria</div>
</div>
<div class="mod in">
<div class="mod-title">Dirección</div>
<div class="mod-sub">Visibilidad ejecutiva</div>
</div>
<div class="mod in">
<div class="mod-title">Operaciones</div>
<div class="mod-sub">Excepciones</div>
</div>
<div class="mod in">
<div class="mod-title">Admin técnico</div>
<div class="mod-sub">Config + RBAC</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">RRHH (BUK)</div>
<div class="mod-sub">Vía BUK API</div>
</div>
</div>
</div>
<!-- Layer 2: Frontend -->
<div class="layer">
<div class="layer-label">
<div class="layer-num">2</div>
<div class="layer-name">Experiencia</div>
<div class="layer-sub">Frontend / Dashboard</div>
</div>
<div class="layer-content">
<div class="mod in">
<div class="mod-title">Auth + RBAC</div>
<div class="mod-sub">4 roles, RLS</div>
</div>
<div class="mod in">
<div class="mod-title">Listado de facturas</div>
<div class="mod-sub">Filtros + búsqueda</div>
</div>
<div class="mod in">
<div class="mod-title">Detalle factura</div>
<div class="mod-sub">PDF + XML + bitácora</div>
</div>
<div class="mod in">
<div class="mod-title">Catálogo clientes</div>
<div class="mod-sub">Lista blanca configurable</div>
</div>
<div class="mod in">
<div class="mod-title">Dashboard CxC</div>
<div class="mod-sub">KPIs + aging</div>
</div>
<div class="mod in">
<div class="mod-title">Cobranza operativa</div>
<div class="mod-sub">Alertas internas</div>
</div>
<div class="mod in">
<div class="mod-title">Config + reportes</div>
<div class="mod-sub">CSV/XLSX export</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">Portal cliente</div>
<div class="mod-sub">Self-service</div>
</div>
</div>
</div>
<!-- Layer 3: Backend -->
<div class="layer">
<div class="layer-label">
<div class="layer-num">3</div>
<div class="layer-name">Núcleo</div>
<div class="layer-sub">Backend / Orquestación</div>
</div>
<div class="layer-content">
<div class="mod in">
<div class="mod-title">Módulo Facturación</div>
<div class="mod-sub">Emisión MXN/USD, estados, vencimientos</div>
</div>
<div class="mod in">
<div class="mod-title">Módulo Cobranza</div>
<div class="mod-sub">Aging + lista blanca</div>
</div>
<div class="mod in">
<div class="mod-title">Motor de reglas</div>
<div class="mod-sub">Alertas + excepciones</div>
</div>
<div class="mod in">
<div class="mod-title">Bitácora / Audit log</div>
<div class="mod-sub">Eventos universales</div>
</div>
<div class="mod in">
<div class="mod-title">Multi-tenancy</div>
<div class="mod-sub">tenant_id + RLS</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Recordatorios email</div>
<div class="mod-sub">Cron + worker</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Pagos Stripe</div>
<div class="mod-sub">Checkout + webhook</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Conciliación bancaria</div>
<div class="mod-sub">Match + cola humana</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Export asientos</div>
<div class="mod-sub">Formato BIND</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">Detección anomalías</div>
<div class="mod-sub">IA / LLM</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">Orquestador agentes IA</div>
<div class="mod-sub">Auto-clasificación</div>
</div>
</div>
</div>
<!-- Layer 4: Data -->
<div class="layer">
<div class="layer-label">
<div class="layer-num">4</div>
<div class="layer-name">Datos</div>
<div class="layer-sub">Persistencia</div>
</div>
<div class="layer-content">
<div class="mod in">
<div class="mod-title">Postgres central</div>
<div class="mod-sub">Azure Flexible</div>
</div>
<div class="mod in">
<div class="mod-title">Clientes + Facturas</div>
<div class="mod-sub">Snapshot normalizado</div>
</div>
<div class="mod partial">
<div class="mod-tag">PARCIAL</div>
<div class="mod-title">Pagos</div>
<div class="mod-sub">Solo lo de BIND en F1</div>
</div>
<div class="mod in">
<div class="mod-title">Alertas + Bitácora</div>
<div class="mod-sub">Eventos + audit</div>
</div>
<div class="mod in">
<div class="mod-title">Configuración + Reglas</div>
<div class="mod-sub">Lista blanca, ventanas</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Storage documental</div>
<div class="mod-sub">PDFs bancarios</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Movimientos bancarios</div>
<div class="mod-sub">Extraídos PDF</div>
</div>
</div>
</div>
<!-- Layer 5: Integraciones externas -->
<div class="layer">
<div class="layer-label">
<div class="layer-num">5</div>
<div class="layer-name">Integraciones</div>
<div class="layer-sub">Sistemas externos</div>
</div>
<div class="layer-content">
<div class="mod in" style="border-width:2.5px">
<div class="mod-tag" style="background:var(--in); color:#fff; border-color:var(--in)">CORE F1</div>
<div class="mod-title">BIND ERP API</div>
<div class="mod-sub">Clientes · Cotizaciones · Facturas (lectura + emisión) · Pagos · PDF/XML</div>
</div>
<div class="mod in">
<div class="mod-title">DOF (TC diario)</div>
<div class="mod-sub">MXN ↔ USD</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Stripe</div>
<div class="mod-sub">Checkout + webhook</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Resend (email)</div>
<div class="mod-sub">Recordatorios cliente</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">Claude API</div>
<div class="mod-sub">Parsing PDFs banco</div>
</div>
<div class="mod f2">
<div class="mod-tag">F2</div>
<div class="mod-title">BUK API</div>
<div class="mod-sub">Nómina + colaboradores</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">Belvo / Plaid</div>
<div class="mod-sub">Bancos en vivo</div>
</div>
<div class="mod f3">
<div class="mod-tag">F3+</div>
<div class="mod-title">Jira API</div>
<div class="mod-sub">Horas → input facturación</div>
</div>
</div>
</div>
</div>
<div class="note-box" style="margin-top:24px">
<span class="note-icon"></span>
<div>
<strong>Arquitectura preparada para el futuro:</strong> los módulos en azul/gris no requieren rearquitectar la base. El motor de reglas, audit log y multi-tenancy de Fase 1 los soporta tal cual cuando se activen. El único costo de diferirlos es <em>tiempo</em>, no <em>rework</em>.
</div>
</div>
</section>
<!-- ============ COMPARE COLUMNS ============ -->
<section>
<p class="section-label">2 / Lo que pidió el PRD vs. lo que entrega Fase 1</p>
<h2 class="block-title">Comparación directa por bloque funcional</h2>
<p class="block-sub">Cada bloque del PRD original (§3.1) mapeado a su entrega real en Fase 1. Diferidos no son "cortes" sino re-secuenciaciones validadas con el CTO.</p>
<div class="compare">
<div class="col prd">
<div class="col-head">
<span class="col-tag prd">PRD original</span>
<div class="col-title">Lo que Balam pidió en el PRD</div>
<div class="col-sub">Plataforma end-to-end con todas las integraciones desde día 1</div>
</div>
<ul>
<li><span class="ico check"></span><div><strong>Facturación visible y consultable</strong><span class="note">Multimoneda MXN/USD/EUR, descarga automática</span></div></li>
<li><span class="ico check"></span><div><strong>Cobranza con seguimiento</strong><span class="note">Lista blanca ACUNTIA + Top 3, registro pagos</span></div></li>
<li><span class="ico check"></span><div><strong>Conciliación bancaria automática</strong><span class="note">3 bancos: 2 MX + IBC Bank Texas</span></div></li>
<li><span class="ico check"></span><div><strong>Asientos contables automáticos</strong><span class="note">Integración con BIND para registro contable</span></div></li>
<li><span class="ico check"></span><div><strong>Dashboard financiero en tiempo real</strong><span class="note">CxC, vencimientos, KPIs directivos</span></div></li>
<li><span class="ico check"></span><div><strong>Alertas automáticas configurables</strong><span class="note">Pagos pendientes, errores conciliación</span></div></li>
<li><span class="ico check"></span><div><strong>Integración con BUK (nómina)</strong><span class="note">Trigger nómina → factura</span></div></li>
<li><span class="ico check"></span><div><strong>Trazabilidad completa</strong><span class="note">Audit log universal</span></div></li>
</ul>
</div>
<div class="col f1">
<div class="col-head">
<span class="col-tag f1">Fase 1 entrega</span>
<div class="col-title">Lo que Fase 1 entrega en 6 7 sem</div>
<div class="col-sub">MVP BIND-first acotado a facturación (consulta + emisión) + cobranza operativa interna</div>
</div>
<ul>
<li><span class="ico check"></span><div><strong>Facturación: consulta y emisión</strong><span class="note">Emite MXN (con IVA) y USD (sin IVA) vía BIND, con dry-run + confirmación humana · EUR diferido a F2</span></div></li>
<li><span class="ico partial">~</span><div><strong>Cobranza con seguimiento interno</strong><span class="note">Lista blanca + aging + alertas <em>internas</em>. Recordatorios a clientes en F2.</span></div></li>
<li><span class="ico out"></span><div><strong>Conciliación bancaria — diferido</strong><span class="note">Cotizado en Anexo B: PDF + Claude API + match</span></div></li>
<li><span class="ico out"></span><div><strong>Asientos contables — diferido</strong><span class="note">Cotizado en Anexo B: export formato BIND</span></div></li>
<li><span class="ico check"></span><div><strong>Dashboard financiero v1</strong><span class="note">CxC totales + por cliente + aging + vencimientos</span></div></li>
<li><span class="ico partial">~</span><div><strong>Alertas internas configurables</strong><span class="note">A equipo de Finanzas. A clientes en F2.</span></div></li>
<li><span class="ico future">·</span><div><strong>BUK — roadmap F3+</strong><span class="note">API confirmada, sin prioridad</span></div></li>
<li><span class="ico check"></span><div><strong>Trazabilidad completa</strong><span class="note">Audit log universal desde día 1</span></div></li>
</ul>
</div>
</div>
</section>
<!-- ============ COVERAGE MATRIX ============ -->
<section>
<p class="section-label">3 / Cobertura granular</p>
<h2 class="block-title">Matriz de requerimientos funcionales (PRD §6)</h2>
<p class="block-sub">Trazabilidad uno a uno de los RF del PRD contra el alcance comprometido en Fase 1.</p>
<div class="matrix">
<table>
<thead>
<tr>
<th style="width:60px">ID</th>
<th>Descripción PRD</th>
<th style="width:140px">Cobertura</th>
<th>Notas</th>
</tr>
</thead>
<tbody>
<tr><td class="rf-id">RF-01</td><td>Generar facturas automáticamente desde eventos de negocio</td><td><span class="cov-badge partial">Parcial</span></td><td>Creación manual (cotización→factura) incluida en Fase 1; la generación automática desde eventos (Jira/BUK) → Fase 2. BIND timbra.</td></tr>
<tr><td class="rf-id">RF-02</td><td>Soporte multimoneda USD/EUR/MXN</td><td><span class="cov-badge partial">Parcial</span></td><td>MXN + USD en Fase 1. EUR en Fase 2.</td></tr>
<tr><td class="rf-id">RF-03</td><td>Integración con sistemas existentes (BUK / nómina)</td><td><span class="cov-badge f2">Fase 2</span></td><td>API BUK confirmada, no prioridad del CTO.</td></tr>
<tr><td class="rf-id">RF-04</td><td>Registrar pagos automáticamente desde fuentes bancarias</td><td><span class="cov-badge f2">Fase 2</span></td><td>Diferido junto con conciliación bancaria.</td></tr>
<tr><td class="rf-id">RF-05</td><td>Asociar pagos a facturas</td><td><span class="cov-badge partial">Parcial</span></td><td>Lo que reporta BIND vía Payments. Matching avanzado en Fase 2.</td></tr>
<tr><td class="rf-id">RF-06</td><td>Identificar pagos parciales y completos</td><td><span class="cov-badge partial">Parcial</span></td><td>Según expone BIND. Validar en Discovery (Plan A/B/C).</td></tr>
<tr><td class="rf-id">RF-07</td><td>Conciliar automáticamente transacciones bancarias</td><td><span class="cov-badge f2">Fase 2</span></td><td>Cotizado: 3 bancos + Claude API + cola humana.</td></tr>
<tr><td class="rf-id">RF-08</td><td>Detectar discrepancias</td><td><span class="cov-badge f2">Fase 2</span></td><td>Sigue a RF-07.</td></tr>
<tr><td class="rf-id">RF-09</td><td>Generar reportes de conciliación</td><td><span class="cov-badge f2">Fase 2</span></td><td>Sigue a RF-07.</td></tr>
<tr><td class="rf-id">RF-10</td><td>Generar asientos contables automáticos</td><td><span class="cov-badge f2">Fase 2</span></td><td>Depende de capacidad de escritura de la API BIND validada.</td></tr>
<tr><td class="rf-id">RF-11</td><td>Integración con sistema contable</td><td><span class="cov-badge f2">Fase 2</span></td><td>Export formato BIND para carga manual del contador.</td></tr>
<tr><td class="rf-id">RF-12</td><td>Dashboard financiero en tiempo real</td><td><span class="cov-badge in">Fase 1</span></td><td>KPIs sincronizados cada 4h con BIND.</td></tr>
<tr><td class="rf-id">RF-13</td><td>Alertas automáticas configurables</td><td><span class="cov-badge partial">Parcial</span></td><td>Alertas <em>internas</em> en Fase 1. A clientes en Fase 2.</td></tr>
<tr><td class="rf-id">RF-14</td><td>Reportes exportables</td><td><span class="cov-badge in">Fase 1</span></td><td>CSV/XLSX por filtros · reporte operativo configurable.</td></tr>
</tbody>
</table>
</div>
<div class="matrix">
<table>
<thead>
<tr>
<th style="width:80px">ID</th>
<th>Requerimiento no funcional (PRD §7)</th>
<th style="width:140px">Cobertura</th>
<th>Notas</th>
</tr>
</thead>
<tbody>
<tr><td class="rf-id">RNF-01</td><td>Arquitectura modular y escalable</td><td><span class="cov-badge in">Fase 1</span></td><td>Multi-tenancy + RLS desde día 1.</td></tr>
<tr><td class="rf-id">RNF-02</td><td>Alta disponibilidad</td><td><span class="cov-badge partial">Parcial</span></td><td>Single-region en Azure. HA real (multi-region) → Fase 2.</td></tr>
<tr><td class="rf-id">RNF-03</td><td>Seguridad de datos financieros</td><td><span class="cov-badge in">Fase 1</span></td><td>Key Vault, RBAC, audit log, hardening.</td></tr>
<tr><td class="rf-id">RNF-04</td><td>Cumplimiento fiscal (México y Texas)</td><td><span class="cov-badge partial">Parcial</span></td><td>Tax estándar en MVP. Compliance avanzado Texas → Fase 2.</td></tr>
<tr><td class="rf-id">RNF-05</td><td>Integración con APIs externas</td><td><span class="cov-badge in">Fase 1</span></td><td>BIND API + DOF para TC.</td></tr>
<tr><td class="rf-id">RNF-06</td><td>Trazabilidad completa de operaciones</td><td><span class="cov-badge in">Fase 1</span></td><td>Audit log universal desde día 1.</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ============ THREE PILLARS ============ -->
<section>
<p class="section-label">4 / Resumen por fase</p>
<h2 class="block-title">Qué entra, qué se difiere, qué espera</h2>
<p class="block-sub">Vista de tres tiempos para alinear expectativas con Noe, Ara y Pedro.</p>
<div class="pillars">
<div class="pillar in">
<div class="pillar-head">
<span class="pillar-emoji"></span>
<span class="pillar-title">Fase 1 — Facturación-first</span>
</div>
<div class="pillar-sub">6 7 semanas · 112 136 h · $67.2K $81.6K MXN</div>
<ul>
<li>Integración API BIND (lectura + escritura asistida)</li>
<li><strong>Emisión de facturas MXN/USD</strong> con dry-run + confirmación</li>
<li>Vista facturación · estados · filtros · PDF/XML</li>
<li>Catálogo clientes + lista blanca ACUNTIA + Top 3</li>
<li>Cobranza operativa interna (aging + alertas a Finanzas)</li>
<li>Dashboard CxC · KPIs · multimoneda MXN+USD</li>
<li>Reportes CSV/XLSX configurables</li>
<li>Auth + RBAC + multi-tenancy + audit log universal</li>
<li>Prototipo visual navegable en Semana 1</li>
</ul>
</div>
<div class="pillar f2">
<div class="pillar-head">
<span class="pillar-emoji"></span>
<span class="pillar-title">Fase 2 — Anexo B (cotizada)</span>
</div>
<div class="pillar-sub">80 106 h · $48K $63.6K MXN indicativo</div>
<ul>
<li>Recordatorios email automáticos a clientes + templates</li>
<li>Pago con link Stripe Checkout + webhook</li>
<li>Conciliación bancaria PDF (3 bancos) + Claude API</li>
<li>Export asientos contables formato BIND</li>
<li>Integración BUK (lectura nómina + colaboradores)</li>
<li>Soporte EUR · alta disponibilidad multi-region</li>
<li>Compliance avanzado Texas</li>
</ul>
</div>
<div class="pillar f3">
<div class="pillar-head">
<span class="pillar-emoji"></span>
<span class="pillar-title">Fase 3+ — Roadmap (sin cotizar)</span>
</div>
<div class="pillar-sub">Se cotiza al cerrar Fase 2 con datos reales</div>
<ul>
<li>Integración bancaria en vivo (Belvo, Plaid)</li>
<li>Conector BIND para asientos contables automáticos</li>
<li>Integración Jira (horas → input facturación)</li>
<li>Cash-flow proyectado 30/60/90</li>
<li>Domiciliación / pagos recurrentes Stripe</li>
<li>Detección anomalías con IA / LLM</li>
<li>Portal de cliente self-service</li>
<li>Multi-tenancy comercial · productización SaaS</li>
</ul>
</div>
</div>
</section>
<div class="footer">
<span>Balam · Plataforma de Automatización Financiera · Diagrama de cobertura Fase 1 vs PRD</span>
<span>Documento de apoyo a la Propuesta Comercial v2.0 — 28 may 2026</span>
</div>
</body>
</html>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
{
"input_html": "Propuesta-Balam.html",
"output_pdf": "Propuesta-Balam.pdf",
"page_size": "Letter",
"cover_full_bleed": true,
"render_timeout_ms": 30000,
"metadata": {
"title": "Propuesta Comercial — Plataforma de Automatización Financiera · Balam",
"author": "Johann Velazquez",
"subject": "MVP de facturación y cobranza sobre BIND ERP — propuesta v1.1",
"keywords": "propuesta, Balam, BIND ERP, facturación, MVP, v1.1"
},
"footer": {
"text": "Propuesta Comercial · Plataforma de Automatización Financiera · Balam — Confidencial",
"skip_first_page": true,
"skip_pages": [],
"page_number_format": "{page} / {pages}",
"rule": true,
"font_size_pt": 7.5,
"margin_mm": 12,
"color": [0.46, 0.51, 0.57]
},
"fonts": {
"footer_ttf": "C:\\Windows\\Fonts\\calibri.ttf"
},
"toc": {
"_comment": "key -> frase ÚNICA del CUERPO de cada sección (no el título). Evitar la primera letra del drop cap.",
"execsum": "opera hoy un proceso financiero fragmentado",
"1": "la lectura del requerimiento fue clara",
"2": "entrega valor real en el menor tiempo posible",
"3": "horas reales con tarifa transparente",
"4": "se desglosa por entregable, conforme al esquema",
"5": "para la operación y evolución incremental",
"6": "servicios de infraestructura externos, independientes",
"7": "depende de las siguientes condiciones",
"8": "Más allá del código funcional, la entrega incluye",
"9": "Rediseño visual integral o sistema de diseño propio",
"10": "se documenta como solicitud de cambio",
"11": "Sesión de revisión de esta propuesta",
"anexoA": "relaciona cada funcionalidad y requerimiento",
"anexoB": "rangos indicativos, no compromisos contractuales"
}
}
File diff suppressed because it is too large Load Diff