mejor version del wiki 2.0

This commit is contained in:
2026-09-28 17:35:13 -06:00
parent 983e19716d
commit 444706b78e
52 changed files with 4086 additions and 108 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Actualiza el inventario de fuentes; no genera instrucciones operativas.
Uso: python3 scripts/inventario.py [ruta/al/proyecto/compras]
"""
from pathlib import Path
import hashlib, json, re, sys
ROOT = Path(__file__).resolve().parents[1]
SOURCE = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else ROOT.parent / 'compras'
FRONT = SOURCE / 'erendira_frontend/src'
BACK = SOURCE / 'erendira_backend'
FOLDERS = {'aspectos':'administracion/aspectos','autorizaciones':'administracion/reglas','catalogos':'administracion/catalogos','categoria_productos':'administracion/categorias-productos','categorias':'administracion/grupos','centros_costo':'administracion/centros','clasificaciones':'administracion/clasificaciones','configuraciones':'administracion/reglas','configuracionesSat':'administracion/configuracion-sat','contratos':'operacion/contratos','cotizaciones':'operacion/cotizaciones','cuentas':'administracion/cuentas','empresas':'administracion/empresas','entregas':'operacion/recepciones','facturas':'finanzas/facturas','invitaciones':'operacion/concursos','ordenes':'operacion/ordenes','pagos':'finanzas/pagos','plantas':'administracion/almacenes','productos':'administracion/items','proveedores':'administracion/proveedores','reportes':'operacion/reportes','solicitudes':'operacion/solicitudes','subclasificaciones':'administracion/subclasificaciones','usuarios':'administracion/usuarios'}
OVERRIDES = {'HomeView.vue':'operacion/listados','Registro.vue':'finanzas/expediente','RegistroExitoso.vue':'finanzas/expediente','usuarios/MiCuenta.vue':'guias/primeros-pasos','ordenes/OrdenesProveedor.vue':'operacion/proveedor-pedidos','proveedores/OrdenesVer.vue':'operacion/proveedor-pedidos','proveedores/ProovedoresExpediente.vue':'finanzas/expediente','facturas/FacturasMultiple.vue':'finanzas/cargar-factura'}
CONTROLLERS = {
'Aspecto':'administracion/aspectos','Autorizacion':'administracion/reglas','CatalogosMonedas':'finanzas/cargar-factura','Categoria':'administracion/grupos','CategoriaProducto':'administracion/categorias-productos','CentroCosto':'administracion/centros','Clasificacion':'administracion/clasificaciones','ClasificacionGasto':'referencia/observaciones','Compra':'finanzas/cargar-factura','Configuracion':'administracion/reglas','Configuraciones':'administracion/configuracion-sat','ContratosMarco':'operacion/contratos','ContratosProductosMarco':'operacion/contratos','Cotizacion':'operacion/cotizaciones','Cuenta':'administracion/cuentas','Dashboard':'operacion/listados','Empresa':'administracion/empresas','Entrega':'operacion/recepciones','Expedientes':'finanzas/expediente','Facturas':'finanzas/facturas','FormasPago':'finanzas/pagos','Grupo':'administracion/subclasificaciones','Inicio':'guias/primeros-pasos','Invitacion':'operacion/concursos','Mail':'referencia/observaciones','Motivo':'operacion/solicitudes','MotivoRechazo':'operacion/solicitudes','Notificaciones':'guias/primeros-pasos','OrdenCompra':'operacion/ordenes','PagoManual':'finanzas/pagos','Planta':'administracion/almacenes','Producto':'administracion/items','Proveedor':'administracion/proveedores','Register':'finanzas/expediente','Reporte':'operacion/reportes','Seguridad':'operacion/solicitudes','Solicitud':'operacion/solicitudes','Tipo':'operacion/solicitudes','TrackedMail':'guias/primeros-pasos','User':'guias/primeros-pasos','Usuario':'administracion/usuarios','Util':'referencia/estados','Home':'operacion/listados','Login':'guias/primeros-pasos','Controller':'referencia/observaciones'}
def clean(s):
return re.sub(r'<!--.*?-->|/\*.*?\*/', '', s, flags=re.S)
def entry(path, guide):
s=path.read_text()
return {'archivo':str(path.relative_to(SOURCE)), 'guia':guide, 'sha256':hashlib.sha256(s.encode()).hexdigest()}
def link(guide):
return f'[Abrir guía](/{guide}/)'
router=(FRONT/'router/index.js').read_text()
imports=dict(re.findall(r"import\s+(\w+)\s+from\s+['\"](?:\.\./|@/)views/([^'\"]+)",router))
routes={}
for match in re.finditer(r"path:\s*['\"]([^'\"]+)['\"]",router):
chunk=router[match.end():]; end=re.search(r"\n\s*path:",chunk); chunk=chunk[:end.start()] if end else chunk
lazy=re.search(r"component:\s*\(\)\s*=>\s*import\(['\"]\.\./views/([^'\"]+)",chunk)
named=re.search(r'component:\s*(\w+)',chunk)
file=lazy.group(1) if lazy else imports.get(named.group(1)) if named else None
# Stop at the next route object: redirects have no component.
if file and 'redirect:' not in chunk[:chunk.find('component:')]: routes.setdefault(file,[]).append(match.group(1))
rows=[]
for p in sorted((FRONT/'views').rglob('*.vue')):
name=str(p.relative_to(FRONT/'views')); folder=name.split('/')[0]
guide=OVERRIDES.get(name,FOLDERS.get(folder,'guias/primeros-pasos'))
x=entry(p,guide); x['rutas']=routes.get(name,[]);rows.append(x)
controllers=[]
for p in sorted((BACK/'app/Http/Controllers').rglob('*.php')):
key=p.stem.removesuffix('Controller') or 'Controller'
if key not in CONTROLLERS: raise ValueError(f'Falta revisar controlador: {p}')
x=entry(p,CONTROLLERS[key]);s=clean(p.read_text())
x['metodos']=re.findall(r'\bfunction\s+(\w+)\s*\(',s)
controllers.append(x)
components=[]
for p in sorted((FRONT/'components').rglob('*.vue')):
name=str(p.relative_to(FRONT/'components')); folder=name.split('/')[0]
if folder=='factura': guide='finanzas/cargar-factura' if p.stem=='SubirFactura' else 'finanzas/facturas'
elif folder=='Expedinete': guide='finanzas/expediente'
elif folder=='ConfiguracionesSat': guide='administracion/configuracion-sat'
elif folder=='invitaciones' or p.stem=='AtencionInvitacion': guide='operacion/concursos'
elif folder=='ordenes' or p.stem=='VerFacturas': guide='operacion/evidencias'
elif folder=='solicitudes': guide='operacion/solicitudes'
elif p.stem in ('detallePO','OrdenCompra'): guide='operacion/ordenes'
else: guide='guias/primeros-pasos'
components.append(entry(p,guide))
blades=[]
for p in sorted((BACK/'resources/views').rglob('*.blade.php')):
name=str(p.relative_to(BACK/'resources/views'))
if 'cuadroComparativo' in name: guide='operacion/concursos'
elif 'orden_compra' in name or name=='mails/po.blade.php': guide='operacion/ordenes'
elif 'solpes' in name: guide='operacion/listados'
elif name.startswith('pdfs/'): guide='finanzas/facturas'
else: guide='guias/primeros-pasos'
blades.append(entry(p,guide))
for x in rows+controllers+components+blades:
if not (ROOT/'src/content/docs'/f"{x['guia']}.md").exists(): raise ValueError(x)
data={'proyecto':'compras','vistas_vue':rows,'componentes_vue':components,'controladores_laravel':controllers,'plantillas_blade':blades}
(ROOT/'analysis').mkdir(exist_ok=True)
(ROOT/'analysis/inventario.json').write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n')
out='''---
title: "Inventario de pantallas y controladores"
description: "Trazabilidad entre el código de compras y las guías del manual."
---
Este inventario permite mantener el manual cuando cambie la aplicación. Las rutas con `:parametro` son patrones de la aplicación, no enlaces del manual. Una guía puede documentar varias pantallas del mismo proceso.
'''
out+=f'Cobertura: **{len(rows)} vistas Vue**, **{len(components)} componentes Vue**, **{len(controllers)} controladores Laravel** y **{len(blades)} plantillas Blade**. El inventario registra archivos y guías; no sustituye una prueba con usuarios y datos de producción.\n\n'
out+='## Pantallas Vue\n\n| Archivo de vista | Ruta de aplicación | Documentación |\n| --- | --- | --- |\n'
for x in rows:
out+=f"| `{x['archivo'].split('/views/')[1]}` | "+ ('<br />'.join('`'+r+'`' for r in x['rutas']) or 'Sin ruta Vue registrada')+f" | {link(x['guia'])} |\n"
out+='\n## Controladores Laravel\n\nLos controladores de catálogos, documentos y acciones se relacionan con su guía. Utilidades, notificaciones y catálogos auxiliares no siempre tienen una pantalla independiente. `MailController`, el controlador base y `Http/Controllers/UserController` no contienen acciones operativas propias; `ClasificacionGastoController` contiene funciones heredadas sin vista dedicada en este frontend.\n\n| Controlador | Acciones identificadas | Documentación |\n| --- | --- | --- |\n'
for x in controllers:
out+=f"| `{x['archivo'].split('Controllers/')[1]}` | "+', '.join('`'+m+'`' for m in x['metodos'])+f" | {link(x['guia'])} |\n"
out+='\n## Componentes y diálogos\n\nLos componentes de formulario se explican dentro de su pantalla de origen. Los componentes de presentación (fechas, importes, paginación y mensajes) no requieren datos adicionales.\n\n| Componente | Guía relacionada |\n| --- | --- |\n'
for x in components: out+=f"| `{x['archivo'].split('/components/')[1]}` | {link(x['guia'])} |\n"
out+='\n## Plantillas del backend\n\nLas plantillas Blade son salidas de impresión, correos o estructuras de presentación. No son formularios adicionales de captura del frontend Vue. Se revisaron como soporte de notificaciones y documentos descargables.\n\n| Plantilla | Guía relacionada |\n| --- | --- |\n'
for x in blades: out+=f"| `{x['archivo'].split('/views/')[1]}` | {link(x['guia'])} |\n"
out+='\n## Mantenimiento\n\nEl repositorio del manual incluye `analysis/inventario.json` con huellas SHA-256 de los archivos revisados y `scripts/inventario.py` para actualizar el inventario. Actualiza primero las instrucciones funcionales si cambia una pantalla; regenerar la tabla no actualiza el contenido del manual.\n'
(ROOT/'src/content/docs/referencia/cobertura.md').write_text(out)
print(f'Inventario: {len(rows)} vistas, {len(components)} componentes, {len(controllers)} controladores, {len(blades)} plantillas.')