#!/usr/bin/env python3
"""Generate 6 SPICA CAPITAL bond documents — Aserta-style poliza format."""

import hashlib
import os
from datetime import datetime
from zoneinfo import ZoneInfo

OUT = os.path.join(os.path.dirname(__file__), "bonds_tmed2")
TZ = ZoneInfo("America/Monterrey")
NOW = datetime(2026, 5, 20, 9, 15, 42, tzinfo=TZ)
FECHA_ES = "20 de mayo de 2026"
FECHA_ISO = NOW.strftime("%Y-%m-%dT%H:%M:%S-06:00")
CERT_SERIAL = "00001000000510767895"
TS = "20/05/2026 09:15:42 CST"

SPICA = {"razon": "SPICA CAPITAL, S.A.P.I. DE C.V., SOFOM, E.N.R.", "rfc": "SCA220617NN8",
         "dom": "Fermín Espinoza Armillita #1000 Int. B, Col. Topo Chico, C.P. 25284, Saltillo, Coahuila, México",
         "tel": "(844) 123-4567", "email": "legal@spicacapital.mx"}
HTS = {"razon": "HYUNDAI TRANSYS MEXICO POWERTRAIN S. DE R.L. DE C.V.", "rfc": "HPM1409018L8"}
BODE = {"razon": "PROYECTOS BODE, S.A. DE C.V.", "rfc": "PBO180817PQ2"}

BONDS = [
    {"ref": "SPC-ANT-2026-347", "type": "ANT", "po": "P260521549", "scope": "INSTALACIÓN ELÉCTRICA TMED II +50K", "amount": 1_289_479.20, "total": 4_298_264.00, "pct": "30"},
    {"ref": "SPC-ANT-2026-348", "type": "ANT", "po": "P260521550", "scope": "MONTAJE DE MAQUINARIA TMED II +50K", "amount": 1_200_000.00, "total": 4_000_000.00, "pct": "30"},
    {"ref": "SPC-CUM-2026-349", "type": "CUM", "po": "P260521549", "scope": "INSTALACIÓN ELÉCTRICA TMED II +50K", "amount": 429_826.40, "total": 4_298_264.00, "pct": "10"},
    {"ref": "SPC-CUM-2026-350", "type": "CUM", "po": "P260521550", "scope": "MONTAJE DE MAQUINARIA TMED II +50K", "amount": 400_000.00, "total": 4_000_000.00, "pct": "10"},
    {"ref": "SPC-VIC-2026-351", "type": "VIC", "po": "P260521549", "scope": "INSTALACIÓN ELÉCTRICA TMED II +50K", "amount": 429_826.40, "total": 4_298_264.00, "pct": "10"},
    {"ref": "SPC-VIC-2026-352", "type": "VIC", "po": "P260521550", "scope": "MONTAJE DE MAQUINARIA TMED II +50K", "amount": 400_000.00, "total": 4_000_000.00, "pct": "10"},
]

TIPO_LABEL = {"ANT": "GARANTÍA DE ANTICIPO", "CUM": "GARANTÍA DE CUMPLIMIENTO", "VIC": "GARANTÍA POR VICIOS OCULTOS"}

VIGENCIA = {
    "ANT": "ESTA GARANTÍA ESTARÁ EN VIGOR DESDE LA FECHA DE SU EXPEDICIÓN Y HASTA LA FECHA DE RECEPCIÓN DE LOS TRABAJOS ESTABLECIDA EN EL CONTRATO, PREVISTA PARA EL 12 DE JULIO DE 2026, O HASTA QUE EL BENEFICIARIO CONFIRME POR ESCRITO LA CORRECTA APLICACIÓN DEL ANTICIPO, LO QUE OCURRA PRIMERO.",
    "CUM": "ESTA GARANTÍA ESTARÁ EN VIGOR DESDE LA FECHA DE SU EXPEDICIÓN. LAS OBLIGACIONES DERIVADAS DE LA PRESENTE GARANTÍA PERMANECERÁN EN PLENO VIGOR Y EFECTO AUN DESPUÉS DE LA EMISIÓN DE LA CARTA DE ACEPTACIÓN FINAL, HASTA QUE EL BENEFICIARIO CONFIRME POR ESCRITO EL CUMPLIMIENTO TOTAL Y DEFINITIVO DE TODAS LAS OBLIGACIONES CONTRACTUALES.",
    "VIC": "ESTA GARANTÍA ESTARÁ EN VIGOR A PARTIR DE LA FECHA DE EMISIÓN DE LA CARTA DE ACEPTACIÓN FINAL, PREVISTA PARA EL 31 DE DICIEMBRE DE 2026, CON UNA VIGENCIA DE DOCE (12) MESES, ES DECIR, HASTA EL 31 DE DICIEMBRE DE 2027.",
}

PARA_TEXT = {
    "ANT": "PARA GARANTIZAR POR: {fiado}, LA DEBIDA INVERSIÓN, AMORTIZACIÓN O DEVOLUCIÓN PARCIAL O TOTAL, EN SU CASO, DEL ANTICIPO POR LA CANTIDAD DE {monto} ({monto_letra}), QUE POR IGUAL SUMA RECIBA DEL BENEFICIARIO DE ESTA GARANTÍA, CON MOTIVO Y A CUENTA DEL CONTRATO CELEBRADO ENTRE LAS PARTES, RELATIVO A: {scope}, CUYAS CARACTERÍSTICAS SE ESPECIFICAN EN EL MISMO DOCUMENTO Y AQUÍ SE DAN POR REPRODUCIDAS CON UN MONTO TOTAL DE {total} ({total_letra}).",
    "CUM": "PARA GARANTIZAR POR: {fiado}, EL FIEL Y EXACTO CUMPLIMIENTO DE TODAS Y CADA UNA DE LAS OBLIGACIONES A SU CARGO DERIVADAS DEL CONTRATO CELEBRADO CON EL BENEFICIARIO, POR LA CANTIDAD DE {monto} ({monto_letra}), EQUIVALENTE AL {pct}% DEL VALOR TOTAL DEL CONTRATO, RELATIVO A: {scope}, CUYAS CARACTERÍSTICAS SE ESPECIFICAN EN EL MISMO DOCUMENTO Y AQUÍ SE DAN POR REPRODUCIDAS CON UN MONTO TOTAL DE {total} ({total_letra}).",
    "VIC": "PARA GARANTIZAR POR: {fiado}, LA CORRECCIÓN DE DEFECTOS, VICIOS OCULTOS Y DEFICIENCIAS QUE RESULTEN EN LOS TRABAJOS EJECUTADOS CON MOTIVO DEL CONTRATO CELEBRADO CON EL BENEFICIARIO, POR LA CANTIDAD DE {monto} ({monto_letra}), EQUIVALENTE AL {pct}% DEL VALOR TOTAL DEL CONTRATO, RELATIVO A: {scope}, CUYAS CARACTERÍSTICAS SE ESPECIFICAN EN EL MISMO DOCUMENTO Y AQUÍ SE DAN POR REPRODUCIDAS CON UN MONTO TOTAL DE {total} ({total_letra}).",
}


def fmt(v):
    return f"${v:,.2f}"


def letra(v):
    int_part = int(v)
    cents = round((v - int_part) * 100)
    millions = int_part // 1_000_000
    thousands = (int_part % 1_000_000) // 1_000
    ones = int_part % 1_000
    parts = []
    if millions == 1: parts.append("UN MILLÓN")
    elif millions > 1: parts.append(f"{millions} MILLONES")
    if thousands > 0: parts.append("MIL" if thousands == 1 else f"{thousands} MIL")
    if ones > 0: parts.append(str(ones))
    text = " ".join(parts) if parts else "CERO"
    return f"{text} PESOS {cents:02d}/100 M.N."


def build_html(b, sha="PENDING"):
    t = b["type"]
    para = PARA_TEXT[t].format(
        fiado=BODE["razon"], monto=fmt(b["amount"]), monto_letra=letra(b["amount"]),
        total=fmt(b["total"]), total_letra=letra(b["total"]), scope=b["scope"], pct=b["pct"])

    return f"""<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>{b['ref']}</title>
<style>
  @page {{ size: letter; margin: 0.2in; }}
  * {{ box-sizing: border-box; }}
  body {{
    font-family: "Arial", "Helvetica", sans-serif;
    font-size: 9.5pt;
    line-height: 1.4;
    color: #000;
    background: #ccc;
    margin: 0;
  }}
  .page {{
    background: #fff;
    max-width: 8.5in;
    margin: 10px auto;
    padding: 0.25in 0.35in;
    box-shadow: 0 1px 4px rgba(0,0,0,.15);
  }}
  /* Header */
  .hdr {{
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    border-bottom: 3px solid #c8960c;
    padding-bottom: 8px;
    margin-bottom: 12px;
  }}
  .hdr-left {{
    display: flex;
    gap: 10px;
    align-items: flex-start;
  }}
  .hdr-left img {{ height: 48px; }}
  .hdr-info {{ font-size: 7.5pt; line-height: 1.3; }}
  .hdr-info .name {{ font-weight: bold; font-size: 8.5pt; }}
  .hdr-right {{
    border: 1px solid #999;
    padding: 4px 8px;
    font-size: 8pt;
    line-height: 1.5;
  }}
  .hdr-right td {{ padding: 1px 6px; }}
  .hdr-right td:first-child {{ font-weight: bold; white-space: nowrap; }}
  .hdr-right td:last-child {{ text-align: right; }}
  /* Poliza box */
  .poliza-bar {{
    background: #f2f2f2;
    border: 1px solid #bbb;
    padding: 6px 10px;
    margin-bottom: 10px;
    font-size: 8.5pt;
  }}
  .poliza-bar b {{ font-size: 9pt; }}
  .poliza-row {{ display: flex; justify-content: space-between; flex-wrap: wrap; gap: 4px 20px; margin-top: 4px; }}
  .poliza-row span {{ white-space: nowrap; }}
  /* Body */
  .constitucion {{ font-size: 8.5pt; margin-bottom: 8px; }}
  .ante {{ font-size: 10pt; font-weight: bold; text-align: center; margin: 12px 0; }}
  .para {{ font-size: 10pt; text-align: justify; margin: 0 0 10px 0; text-transform: uppercase; }}
  .vigencia {{ font-size: 9.5pt; text-align: justify; margin-bottom: 10px; }}
  .fin {{ font-weight: bold; margin: 16px 0 12px 0; }}
  /* Clausulas */
  .clausulas {{
    border-top: 1px solid #999;
    margin-top: 10px;
    padding-top: 6px;
    font-size: 7pt;
    text-align: justify;
    line-height: 1.35;
    color: #222;
  }}
  .clausulas b {{ font-size: 7.5pt; }}
  /* Signature */
  .sig-area {{
    display: flex;
    justify-content: flex-end;
    margin-top: 10px;
    margin-bottom: 6px;
  }}
  .sig-box {{ text-align: center; width: 45%; }}
  .sig-box .line {{ border-top: 1px solid #000; margin-bottom: 3px; }}
  .sig-box .name {{ font-size: 8pt; font-weight: bold; }}
  /* Validation */
  .validacion {{
    text-align: center;
    border-top: 1px solid #999;
    border-bottom: 1px solid #999;
    padding: 6px 0;
    margin: 8px 0;
  }}
  .validacion .label {{ font-size: 8pt; font-weight: bold; }}
  .validacion .code {{ font-size: 11pt; font-weight: bold; font-family: monospace; }}
  /* CONDUSEF */
  .registro {{
    font-size: 7.5pt;
    text-align: center;
    margin: 6px 0;
    font-style: italic;
  }}
  /* FIEL */
  .fiel {{
    font-family: "Courier New", monospace;
    font-size: 6.5pt;
    border: 1px solid #aaa;
    padding: 4px 8px;
    background: #fafafa;
    margin-top: 6px;
    line-height: 1.35;
  }}
  /* Footer */
  .foot {{
    display: flex;
    justify-content: space-between;
    font-size: 6.5pt;
    color: #666;
    margin-top: 6px;
    border-top: 2px solid #c8960c;
    padding-top: 4px;
  }}
  @media print {{
    body {{ background: #fff; }}
    .page {{ margin: 0; padding: 0.2in 0.3in; box-shadow: none; max-width: 100%; width: 100%; }}
  }}
</style>
</head>
<body>
<div class="page">

  <!-- HEADER -->
  <div class="hdr">
    <div class="hdr-left">
      <img src="spica.jpeg" alt="">
      <div class="hdr-info">
        <div class="name">{SPICA['razon']}</div>
        RFC: {SPICA['rfc']}<br>
        {SPICA['dom']}<br>
        <b>Teléfono:</b> {SPICA['tel']}
      </div>
    </div>
    <table class="hdr-right">
      <tr><td>Garantía Número:</td><td>{b['ref']}</td></tr>
      <tr><td>Folio:</td><td>{b['ref'].replace('SPC-','')}</td></tr>
      <tr><td>Monto de la garantía:</td><td>{fmt(b['amount'])}</td></tr>
      <tr><td>Monto de este movimiento:</td><td>{fmt(b['amount'])}</td></tr>
    </table>
  </div>

  <!-- POLIZA BAR -->
  <div class="poliza-bar">
    <b>PÓLIZA DE GARANTÍA FINANCIERA</b>
    <div class="poliza-row">
      <span><b>Lugar y Fecha de Expedición:</b> Saltillo, Coahuila, {FECHA_ES}</span>
      <span><b>Moneda:</b> MXN</span>
    </div>
    <div class="poliza-row">
      <span><b>Movimiento:</b> Emisión</span>
      <span><b>Vigencia:</b> De conformidad con el texto de la garantía.</span>
    </div>
    <div class="poliza-row">
      <span><b>Fiado:</b> {BODE['razon']}</span>
    </div>
  </div>

  <!-- CONSTITUCION -->
  <p class="constitucion">
    {SPICA['razon']}, en su carácter de entidad financiera legalmente constituida conforme a las leyes de los Estados Unidos Mexicanos, con capacidad para asumir obligaciones mercantiles de pago y emitir garantías financieras al amparo de los artículos 5, 79 y 82 de la Ley General de Títulos y Operaciones de Crédito, se constituye garante solidaria:
  </p>
  <p class="constitucion">
    Ante {HTS['razon']}
  </p>

  <!-- ANTE -->
  <div class="ante">ANTE: {HTS['razon']}</div>

  <!-- PARA -->
  <p class="para">{para}</p>

  <!-- VIGENCIA -->
  <p class="vigencia">{VIGENCIA[t]}</p>

  <!-- FIN -->
  <p class="fin">=FIN DE TEXTO=</p>

  <!-- CLAUSULAS -->
  <div class="clausulas">
    <b>PARA VALIDAR LA AUTENTICIDAD DE ESTA GARANTÍA VERIFIQUE EL HASH SHA-256 INDICADO AL PIE.</b><br>
    <b>CLAUSULAS IMPORTANTES AL FINAL DE ESTA PÓLIZA</b><br><br>
    Esta garantía financiera es emitida por una entidad financiera privada registrada (SOFOM E.N.R.) y no constituye una póliza de fianza regulada por la Ley Federal de Instituciones de Seguros y de Fianzas ni implica supervisión de la CNSF. Se emite al amparo de la libertad contractual y las obligaciones solidarias previstas en los artículos 5, 79 y 82 de la Ley General de Títulos y Operaciones de Crédito, así como los artículos 89 a 97 del Código de Comercio en materia de firma electrónica avanzada.<br><br>
    1.- En toda garantía otorgada por LA GARANTE, sus derechos y obligaciones se rigen por las leyes mercantiles federales de los Estados Unidos Mexicanos. Para la interpretación y cumplimiento de la presente garantía, las partes se someten expresamente a la jurisdicción de los tribunales competentes en Saltillo, Coahuila, renunciando a cualquier otro fuero que pudiera corresponderles.<br>
    2.- En caso de PÉRDIDA O EXTRAVÍO DE LA GARANTÍA, el BENEFICIARIO podrá ejercer su derecho de reclamación comprobando por escrito que la garantía fue otorgada, solicitando a LA GARANTE un duplicado de la garantía emitida a su favor.<br>
    3.- Se PRESUMIRÁ EXTINCIÓN de obligación como fiadora cuando se haya la devolución de una garantía a LA GARANTE, salvo prueba en contrario.<br>
    4.- La presente garantía es de naturaleza directa, solidaria, irrevocable e incondicional. LA GARANTE responderá frente al BENEFICIARIO hasta por el monto máximo garantizado indicado en el cuerpo de esta póliza, sin necesidad de requerimiento previo al FIADO.<br>
    5.- Para hacer efectiva la presente garantía, el BENEFICIARIO deberá presentar reclamación directamente y por escrito, en el domicilio de LA GARANTE, acompañando: (a) copia de la garantía; (b) documentación que acredite el incumplimiento del FIADO; (c) cuantificación del monto reclamado, que en ningún caso podrá exceder el monto máximo garantizado. LA GARANTE dispondrá de quince (15) días hábiles bancarios para resolver sobre la procedencia.<br>
    6.- LA GARANTE no será responsable por: penalizaciones convencionales, daños indirectos, lucro cesante, pérdida de producción, ni por incumplimientos derivados de fuerza mayor, caso fortuito, actos de autoridad, o causas atribuibles al BENEFICIARIO.<br>
    7.- En caso de pago al BENEFICIARIO, LA GARANTE quedará subrogada de pleno derecho, hasta por la cantidad efectivamente pagada, en todos los derechos y acciones que el BENEFICIARIO tenga contra el FIADO.<br>
    8.- Reclamaciones presentadas sin evidencia suficiente, fuera del plazo de vigencia, o por conceptos distintos a los cubiertos por la presente garantía, no serán exigibles y no generarán obligación de pago para LA GARANTE. LA GARANTE podrá requerir peritaje técnico independiente o evidencia documental adicional antes de resolver la procedencia definitiva de cualquier reclamación. Cualquier intento de utilizar esta garantía para forzar descuentos comerciales, retenciones indebidas o compensaciones ajenas al objeto garantizado se considerará uso abusivo, y LA GARANTE podrá rechazar la reclamación y documentar la negativa.<br>
    9.- De conformidad con el artículo 214 de la Ley de Instituciones de Seguros y de Fianzas, LA GARANTE podrá usar equipos y medios electrónicos, y el uso de los medios de identificación en sustitución de la firma autógrafa producirá los mismos efectos que las leyes otorgan a los documentos correspondientes y tendrán el mismo valor probatorio. Con la aceptación de esta garantía, el BENEFICIARIO manifiesta expresamente su consentimiento para que {SPICA['razon']} emita las garantías y los documentos modificatorios a su favor, indistintamente en forma escrita o a través de medios electrónicos. Esta garantía es la impresión de un documento digital el cual ha sido emitido de conformidad con las disposiciones del Código de Comercio respecto a la Firma Electrónica Avanzada. Con la aceptación de esta garantía y con el fin de establecer que la firma electrónica provista de LA GARANTE, se acuerda en los términos del artículo 90 bis del Código de Comercio, como procedimiento de identificación y validación de la misma, la obtención del certificado de autenticidad.<br>
    10.- CONTROVERSIAS.- Para conocer y resolver de las controversias derivadas de las garantías a que se refiere la presente, serán competentes las autoridades mexicanas, sometiéndose a la jurisdicción de los Tribunales de Saltillo, Coahuila, y/o Monterrey, Nuevo León, renunciando a cualquier otro fuero.
  </div>

  <!-- VALIDACION -->
  <div class="validacion">
    <div class="label">LÍNEA DE VALIDACIÓN</div>
    <div class="code">{b['ref']}</div>
  </div>

  <!-- REGISTRO -->
  <div class="registro">
    Garantía financiera mercantil privada emitida por entidad registrada ante CONDUSEF.<br>
    {SPICA['razon']} — RFC {SPICA['rfc']} — SOFOM, Entidad No Regulada.
  </div>

  <!-- FIEL -->
  <div class="fiel">
    Firma Electrónica Avanzada (e.firma / FIEL) — RFC: {SPICA['rfc']} — Cert. SAT: {CERT_SERIAL} — {TS}<br>
    SHA-256: {sha}
  </div>

  <!-- FOOTER -->
  <div class="foot">
    <span>E/R {FECHA_ES.upper()}</span>
    <span>{SPICA['email']}</span>
  </div>

</div>
</body>
</html>"""


def build_xml(b, sha_hash):
    t = b["type"]
    return f"""<?xml version="1.0" encoding="UTF-8"?>
<GarantiaFinanciera version="1.0" esquema="SPICA-{t}" folioInterno="{b['ref']}" fechaEmision="{FECHA_ISO}" lugarEmision="Saltillo, Coahuila, MX" monedaPrincipal="MXN">
    <Garante razonSocial="{SPICA['razon']}" rfc="{SPICA['rfc']}" tipoEntidad="SOFOM_ENR"/>
    <Beneficiario razonSocial="{HTS['razon']}" rfc="{HTS['rfc']}"/>
    <Fiado razonSocial="{BODE['razon']}" rfc="{BODE['rfc']}"/>
    <Cobertura monto="{b['amount']:.2f}" moneda="MXN" tipo="{TIPO_LABEL[t]}"/>
    <FirmaElectronica rfcFirmante="{SPICA['rfc']}" serieCertificado="{CERT_SERIAL}" selloTiempo="{FECHA_ISO}">
        <Hash algoritmo="SHA-256">{sha_hash}</Hash>
    </FirmaElectronica>
</GarantiaFinanciera>"""


def main():
    os.makedirs(OUT, exist_ok=True)
    for b in BONDS:
        html = build_html(b, sha="PENDING")
        sha = hashlib.sha256(html.encode("utf-8")).hexdigest()
        html = build_html(b, sha=sha)
        sha_final = hashlib.sha256(html.encode("utf-8")).hexdigest()
        with open(os.path.join(OUT, f"{b['ref']}.html"), "w") as f:
            f.write(html)
        with open(os.path.join(OUT, f"{b['ref']}_integridad.xml"), "w") as f:
            f.write(build_xml(b, sha_final))
        print(f"OK: {b['ref']} ({b['type']}) — {fmt(b['amount'])}")
    print(f"\n6 bonds in {OUT}")


if __name__ == "__main__":
    main()
