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>
This commit is contained in:
@@ -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 ~6–10 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] 0–1),
|
||||
`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`.
|
||||
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.
@@ -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">6–7</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> ✅ Covered · ⚠️ Partial · ❌ 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
@@ -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
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
> **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:** 🟢 **Avanzando** — propuesta v1.0 enviada y revisada; **anticipo de Discovery aprobado**. Pendiente: formalización por correo de Balam + propuesta v1.1 con ajustes.
|
||||
_Última actualización: 2026-06-04._
|
||||
**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._
|
||||
|
||||
---
|
||||
|
||||
@@ -17,10 +17,12 @@ Plataforma web para centralizar y automatizar **facturación y cobranza** de Bal
|
||||
|---|---|
|
||||
| [`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 v1.0 (MD)](propuesta/00%20-%20PROPUESTA-COMERCIAL.md), [PDF enviado](propuesta/Propuesta-Balam.pdf), [prototipo](propuesta/prototipo-mvp-fase1.html), [diagrama Fase 1 vs PRD](propuesta/diagrama-fase1-vs-prd.html). |
|
||||
| [`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 (0–3) con fechas, responsables y sesiones, para el seguimiento con Erika (Jira/Gantt). |
|
||||
|
||||
## 3. Datos clave
|
||||
|
||||
@@ -35,16 +37,18 @@ Plataforma web para centralizar y automatizar **facturación y cobranza** de Bal
|
||||
| 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.0)
|
||||
## 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** · **6–7 semanas** (~20 h/sem) · tarifa **$600 MXN/h** · modelo Time & Materials con tope por etapa.
|
||||
- **Anticipo:** 30 h de Discovery = **$18,000 MXN + IVA** ✅ aprobado.
|
||||
- **Pagos:** anticipo a 7 días; avances a 30 días.
|
||||
- **Inversión:** **$67,200 – $81,600 MXN + IVA** · **6–7 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.
|
||||
|
||||
@@ -54,17 +58,24 @@ Plataforma web para centralizar y automatizar **facturación y cobranza** de Bal
|
||||
|---|---|
|
||||
| 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 | **Anticipo de Discovery aprobado.** Pagos avances a 30 días. Gestión en Jira. |
|
||||
| 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. Johann: cambiar régimen fiscal → emitir factura de anticipo.
|
||||
2. Balam (Noe): formalizar por correo (accesos + ajustes).
|
||||
3. Johann: preparar **propuesta v1.1** (conciliación, dashboard, soporte).
|
||||
4. Pedro: manual de marca, llaves API BIND, espacio en Jira.
|
||||
5. Arrancar **Discovery**.
|
||||
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
|
||||
|
||||
|
||||
+28
-13
@@ -2,31 +2,46 @@
|
||||
|
||||
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-04 (tras llamada de revisión de propuesta)._
|
||||
_Última actualización: 2026-06-30 (plan de actividades entregado; kickoff con Noé el 1-jul, 7am)._
|
||||
|
||||
## 🔴 Johann (proveedor)
|
||||
## 🔴 Johann (proveedor) — inmediato
|
||||
|
||||
- [ ] Cambiar régimen fiscal (en proceso, ~24 h) para poder facturar el anticipo.
|
||||
- [ ] Emitir **factura de anticipo** ($18,000 + IVA) cuando Balam la solicite (a partir del 5-jun).
|
||||
- [ ] Preparar **propuesta v1.1** con los ajustes de la junta:
|
||||
- [ ] Evaluar/cotizar **conciliación bancaria** dentro del MVP (no solo Anexo B).
|
||||
- [ ] Ajustar **dashboard/reporteo** para no solapar con el tablero Power BI de Pedro (conservar cobranza + alertas).
|
||||
- [ ] Renegociar **vigencia de las horas de soporte** (30 días le parece poco).
|
||||
- [ ] **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 (0–3)** 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 Discovery una vez recibido anticipo + accesos (puede iniciar inicio de semana).
|
||||
- [ ] **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
|
||||
|
||||
- [ ] **Noe:** formalizar por correo (desde 5-jun) — solicitar factura de anticipo, enviar accesos y ajustes acordados.
|
||||
- [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).
|
||||
- [ ] **Pedro:** crear el espacio del proyecto en **Jira** (plan free) para trazabilidad de avances.
|
||||
- [ ] **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** por Noe (4-jun).
|
||||
- [x] Pagos: anticipo a 7 días; avances a 30 días — aceptado por Johann (4-jun).
|
||||
- [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 (29–30 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).
|
||||
|
||||
+176
-16
@@ -7,10 +7,11 @@
|
||||
- **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`
|
||||
- **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 → 4 jun 2026
|
||||
**Periodo:** 30 abr 2026 → 30 jun 2026
|
||||
**Orden:** cronológico (más antiguo arriba)
|
||||
|
||||
---
|
||||
@@ -97,6 +98,26 @@ Con estas respuestas puedo tener la propuesta lista en 24–48 horas. Si prefier
|
||||
|
||||
---
|
||||
|
||||
## 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,
|
||||
@@ -144,6 +165,15 @@ Buenos días Johan,
|
||||
|
||||
---
|
||||
|
||||
## 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,
|
||||
@@ -183,15 +213,16 @@ En cuanto tengamos mayor claridad sobre los tiempos, te los haremos saber.
|
||||
|
||||
---
|
||||
|
||||
## 10 · ≈ 2–3 jun 2026 · Johann → Noe (envío de la propuesta) · *Elementos enviados*
|
||||
## 10 · May 29, 2026 — 2:44 PM · Johann → Noe (CC Ara, Erika, Pedro) · envío de la propuesta
|
||||
|
||||
> *"Hola Noe, adjunto la propuesta para la plataforma de automatización financiera que conversamos, enfocada en un MVP sobre BIND ERP que cubre fa[cturación]…"* (vista previa)
|
||||
> **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**, **6–7 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 3, 2026 — 4:41 PM · Pedro → Johann (CC Noe)
|
||||
## 11 · Jun 2, 2026 — 5:33 PM · Pedro → Johann, Noe (CC Ara, Erika)
|
||||
|
||||
> **Asunto:** RE: Solicitud de cotización – PRD
|
||||
|
||||
@@ -199,13 +230,13 @@ Envío de la **propuesta v1.0 en PDF** (`Propuesta-Balam.pdf`). MVP BIND-first e
|
||||
|
||||
---
|
||||
|
||||
## 12 · Jun 3, 2026 (tarde) · Johann → Pedro · *Elementos enviados*
|
||||
## 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–4, 2026 · Pedro → Johann (confirmación + liga Teams)
|
||||
## 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)
|
||||
|
||||
@@ -245,6 +276,130 @@ Queda confirmada la sesión: **jueves 4-jun, 7:00 am, por Microsoft Teams**.
|
||||
|
||||
---
|
||||
|
||||
## 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, 6–7 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 (112–136 h, 6–7 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 inicio–fin · 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 29–30, 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 inicio–fin · responsable · apoyo de Balam): primero Etapa 0 y 1 (29-jun) y luego **enviado completo, las 4 etapas (0–3)** con fechas tentativas (**30-jun, 12:34**). Las sesiones quedan ubicadas por etapa; la Etapa 0–1 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
|
||||
@@ -270,14 +425,19 @@ Queda confirmada la sesión: **jueves 4-jun, 7:00 am, por Microsoft Teams**.
|
||||
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 (tras la llamada del 4-jun-2026)
|
||||
**Propuesta v1.0 enviada y revisada. El proyecto avanza: anticipo de Discovery ($18,000) aprobado.** Noe formaliza por correo desde el 5-jun (factura de anticipo + accesos + ajustes).
|
||||
### 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**.
|
||||
|
||||
**Puntos abiertos a resolver en la v1.1 / por correo:**
|
||||
- **Conciliación bancaria:** Noe la quiere sumar al MVP (no esperar); definir si entra de inmediato o tras Discovery según flujo. Es prioridad real del cliente.
|
||||
- **Dashboard:** posible recorte por solaparse con el tablero Power BI de Pedro; conservar el módulo de cobranza + alertas.
|
||||
- **Soporte:** renegociar la vigencia de las horas (30 días le parece poco).
|
||||
- **Pagos:** anticipo 7 días; avances a 30 días (aceptado).
|
||||
- **Accesos:** API BIND vía cuenta maestra ARA (llave de Arturo para dev), Azure con Guajardo, manual de marca de Pedro, reglas de negocio con Arturo.
|
||||
**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.
|
||||
|
||||
**Siguiente entrega de Johann:** factura de anticipo + propuesta ajustada (v1.1) incorporando los comentarios.
|
||||
**Calendario tentativo:** kickoff 1-jul · Etapa 0 (Discovery) sem del 6-jul · Etapa 1 13–24 jul · Etapa 2 27-jul–7-ago · Etapa 3 10–21 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,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 (112–136 h, 6–7 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:** 25–26 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.
|
||||
@@ -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. ~45–60 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, ~6–7 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).
|
||||
@@ -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 ~6–7 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 (5–6 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 (18–22 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 (32–39 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 (38–46 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.5–2 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 (24–29 h): cobranza + alertas + tablero + reportes; cierre del MVP, documentación, capacitación y soporte de 2 semanas.**
|
||||
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
| **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.0 — alcance acotado a facturación sobre BIND ERP |
|
||||
| **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 |
|
||||
|
||||
---
|
||||
@@ -216,10 +216,10 @@ La dedicación es de **media jornada (aproximadamente 20 h/semana**, con flexibi
|
||||
|
||||
### 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 al paquete de mantenimiento opcional (§5).
|
||||
- **Facturación:** semanal, los viernes, por las horas de la semana previa. Cada factura incluye el anexo con el desglose de horas por tarea y etapa.
|
||||
- **Pago:** a 7 días naturales mediante transferencia.
|
||||
- **Anticipo de arranque:** 30 horas de la Etapa 0, equivalentes a **$18,000 MXN** + IVA, para iniciar Discovery. Las horas no consumidas se acreditan a la etapa siguiente. Si Discovery revela bloqueadores que modifiquen el alcance de forma significativa, se replantea el plan antes de continuar.
|
||||
- **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
|
||||
|
||||
@@ -263,24 +263,24 @@ La inversión de la primera etapa se desglosa por entregable, conforme al esquem
|
||||
|
||||
### 5.1 Garantía incluida
|
||||
|
||||
- **Defectos en funcionalidad entregada:** cobertura sin costo durante **30 días** posteriores a la entrega de cada etapa.
|
||||
- **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 Paquete mensual de mantenimiento (opcional)
|
||||
### 5.2 Bolsa de horas de soporte (opcional)
|
||||
|
||||
Concluida la garantía y el soporte incluidos, se ofrece un paquete mensual de horas reservadas para la operación y evolución incremental de la plataforma:
|
||||
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:
|
||||
|
||||
| Paquete | Horas/mes | Inversión mensual (sin IVA) |
|
||||
| Bloque | Horas | Inversión (sin IVA) |
|
||||
|---|---|---|
|
||||
| Básico | 8 h | $4,800 |
|
||||
| Estándar *(recomendado)* | 16 h | $9,600 |
|
||||
| Premium | 24 h | $14,400 |
|
||||
| Pequeño | 10 h | $6,000 |
|
||||
| Mediano *(recomendado)* | 20 h | $12,000 |
|
||||
| Grande | 40 h | $24,000 |
|
||||
|
||||
**Incluye:** corrección de defectos fuera de garantía; ajustes menores y requerimientos pequeños dentro de las horas del paquete; mantenimiento de dependencias y compatibilidad ante cambios de la API de BIND; monitoreo de la plataforma con atención a incidencias en 1 día hábil; y reporte mensual de horas.
|
||||
**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.
|
||||
|
||||
**No incluye:** desarrollo de los módulos diferidos (Anexo B); migraciones o cambios mayores de arquitectura; ni incidentes atribuibles a terceros (Azure, BIND, SAT).
|
||||
**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.
|
||||
|
||||
**Términos:** hasta el 50% de las horas no consumidas se acumula al mes siguiente (no reembolsables); compromiso mínimo de 3 meses, con renovación mensual y aviso de cancelación de 30 días. Su activación es opcional y se define al cierre del MVP.
|
||||
**No cubre:** desarrollo de los módulos diferidos (Anexo B); migraciones o cambios mayores de arquitectura; ni incidentes atribuibles a terceros (Azure, BIND, SAT).
|
||||
|
||||
---
|
||||
|
||||
@@ -345,13 +345,13 @@ Más allá del código funcional, la entrega incluye:
|
||||
- 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 el paquete de mantenimiento, §5.2).
|
||||
- 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 30 días posteriores a la entrega de cada etapa.
|
||||
- **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.
|
||||
@@ -363,8 +363,8 @@ Más allá del código funcional, la entrega incluye:
|
||||
## 11. Próximos pasos
|
||||
|
||||
1. Sesión de revisión de esta propuesta (1 h) para resolver dudas y ajustar lo que corresponda.
|
||||
2. Formalización del acuerdo (contrato de prestación de servicios y acuerdo de confidencialidad).
|
||||
3. Anticipo de 30 horas de la Etapa 0 (**$18,000 MXN** + IVA) para iniciar Discovery.
|
||||
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**.
|
||||
|
||||
---
|
||||
@@ -484,4 +484,4 @@ Se cotiza al cierre de la fase anterior, priorizando con datos reales de uso: in
|
||||
|
||||
### 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, con un anticipo de 30 horas del primero a desarrollar. La tarifa, el esquema, la comunicación y la garantía aplican en los mismos términos que en la primera etapa.
|
||||
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.
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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">6–7</div><div class="lab">Semanas</div></div>
|
||||
<div class="stat"><div class="big">112–136</div><div class="lab">Horas (T&M)</div></div>
|
||||
<div class="stat"><div class="big">$67.2K</div><div class="lab">a $81.6K + 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 · 18–22 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 5–6 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, +6–8 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 · 32–39 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 · 38–46 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 · 24–29 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.5–2 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ínimo–má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 2–3 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:00–18: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 + 32–64 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> (1–3 años, hasta ~30–40% 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> ✅ Cubierto · ⚠️ Parcial · ❌ 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.
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.
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user